Browse Source

Implemented feature interceptor

pull/859/head
Halil ibrahim Kalkan 7 years ago
parent
commit
1845479b24
  1. 1
      framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs
  2. 1
      framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj
  3. 8
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs
  4. 3
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs
  5. 1
      framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs
  6. 10
      framework/src/Volo.Abp.Features/Volo/Abp/Features/DisableFeatureCheckAttribute.cs
  7. 96
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs
  8. 56
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptor.cs
  9. 36
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs
  10. 11
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IMethodInvocationFeatureCheckerService.cs
  11. 14
      framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerContext.cs
  12. 51
      framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerService.cs
  13. 33
      framework/src/Volo.Abp.Features/Volo/Abp/Features/RequiresFeatureAttribute.cs
  14. 0
      framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs
  15. 23
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/ClassFeatureTestService.cs
  16. 2
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs
  17. 2
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs
  18. 76
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureInterceptor_Tests.cs
  19. 2
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureTestBase.cs
  20. 11
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/IMethodFeatureTestService.cs
  21. 23
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/MethodFeatureTestService.cs
  22. 1
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs
  23. 8
      framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs

1
framework/src/Volo.Abp.Core/Volo/Abp/Aspects/AbpCrossCuttingConcerns.cs

@ -12,6 +12,7 @@ namespace Volo.Abp.Aspects
public const string Validation = "AbpValidation";
public const string UnitOfWork = "AbpUnitOfWork";
public const string Authorization = "AbpAuthorization";
public const string FeatureChecking = "AbpFeatureChecking";
public static void AddApplied(object obj, params string[] concerns)
{

1
framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj

@ -17,6 +17,7 @@
<ProjectReference Include="..\Volo.Abp.Authorization\Volo.Abp.Authorization.csproj" />
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
<ProjectReference Include="..\Volo.Abp.Ddd.Domain\Volo.Abp.Ddd.Domain.csproj" />
<ProjectReference Include="..\Volo.Abp.Features\Volo.Abp.Features.csproj" />
<ProjectReference Include="..\Volo.Abp.Http.Abstractions\Volo.Abp.Http.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
<ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" />

8
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs

@ -2,6 +2,7 @@
using Volo.Abp.Application.Services;
using Volo.Abp.Authorization;
using Volo.Abp.Domain;
using Volo.Abp.Features;
using Volo.Abp.Http;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Modularity;
@ -20,7 +21,8 @@ namespace Volo.Abp.Application
typeof(AbpValidationModule),
typeof(AbpAuthorizationModule),
typeof(AbpHttpAbstractionsModule),
typeof(AbpSettingsModule)
typeof(AbpSettingsModule),
typeof(AbpFeaturesModule)
)]
public class AbpDddApplicationModule : AbpModule
{
@ -30,8 +32,8 @@ namespace Volo.Abp.Application
{
options.IgnoredInterfaces.AddIfNotContains(typeof(IRemoteService));
options.IgnoredInterfaces.AddIfNotContains(typeof(IApplicationService));
options.IgnoredInterfaces.AddIfNotContains(typeof(IUnitOfWorkEnabled)); //TODO: Move to it's own module if possible?
options.IgnoredInterfaces.AddIfNotContains(typeof(IAuthorizationEnabled)); //TODO: Move to it's own module if possible?
options.IgnoredInterfaces.AddIfNotContains(typeof(IUnitOfWorkEnabled));
options.IgnoredInterfaces.AddIfNotContains(typeof(IAuthorizationEnabled));
});
}
}

3
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs

@ -9,6 +9,7 @@ using Volo.Abp.Aspects;
using Volo.Abp.Auditing;
using Volo.Abp.Authorization;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
@ -51,6 +52,8 @@ namespace Volo.Abp.Application.Services
public IAuthorizationService AuthorizationService { get; set; }
public IFeatureChecker FeatureChecker { get; set; }
protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current;
protected ILogger Logger => _lazyLogger.Value;

1
framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs

@ -15,6 +15,7 @@ namespace Volo.Abp.Features
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.OnRegistred(FeatureInterceptorRegistrar.RegisterIfNeeded);
AutoAddDefinitionProviders(context.Services);
}

10
framework/src/Volo.Abp.Features/Volo/Abp/Features/DisableFeatureCheckAttribute.cs

@ -0,0 +1,10 @@
using System;
namespace Volo.Abp.Features
{
[AttributeUsage(AttributeTargets.Method)]
public class DisableFeatureCheckAttribute : Attribute
{
}
}

96
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerExtensions.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.Authorization;
using Volo.Abp.Threading;
namespace Volo.Abp.Features
@ -43,5 +45,99 @@ namespace Volo.Abp.Features
{
return AsyncHelper.RunSync(() => featureChecker.IsEnabledAsync(name));
}
public static async Task<bool> IsEnabledAsync(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames)
{
if (featureNames.IsNullOrEmpty())
{
return true;
}
if (requiresAll)
{
foreach (var featureName in featureNames)
{
if (!(await featureChecker.IsEnabledAsync(featureName)))
{
return false;
}
}
return true;
}
foreach (var featureName in featureNames)
{
if (await featureChecker.IsEnabledAsync(featureName))
{
return true;
}
}
return false;
}
public static bool IsEnabled(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames)
{
return AsyncHelper.RunSync(() => featureChecker.IsEnabledAsync(requiresAll, featureNames));
}
public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, string featureName)
{
if (!(await featureChecker.IsEnabledAsync(featureName)))
{
throw new AbpAuthorizationException("Feature is not enabled: " + featureName);
}
}
public static void CheckEnabled(this IFeatureChecker featureChecker, string featureName)
{
if (!featureChecker.IsEnabled(featureName))
{
throw new AbpAuthorizationException("Feature is not enabled: " + featureName);
}
}
public static async Task CheckEnabledAsync(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames)
{
if (featureNames.IsNullOrEmpty())
{
return;
}
if (requiresAll)
{
foreach (var featureName in featureNames)
{
if (!(await featureChecker.IsEnabledAsync(featureName)))
{
throw new AbpAuthorizationException(
"Required features are not enabled. All of these features must be enabled: " +
string.Join(", ", featureNames)
);
}
}
}
else
{
foreach (var featureName in featureNames)
{
if (await featureChecker.IsEnabledAsync(featureName))
{
return;
}
}
throw new AbpAuthorizationException(
"Required features are not enabled. At least one of these features must be enabled: " +
string.Join(", ", featureNames)
);
}
}
public static void CheckEnabled(this IFeatureChecker featureChecker, bool requiresAll, params string[] featureNames)
{
AsyncHelper.RunSync(() => featureChecker.CheckEnabledAsync(requiresAll, featureNames));
}
}
}

56
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptor.cs

@ -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
)
);
}
}
}

36
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureInterceptorRegistrar.cs

@ -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);
}
}
}

11
framework/src/Volo.Abp.Features/Volo/Abp/Features/IMethodInvocationFeatureCheckerService.cs

@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace Volo.Abp.Features
{
public interface IMethodInvocationFeatureCheckerService
{
Task CheckAsync(
MethodInvocationFeatureCheckerContext context
);
}
}

14
framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerContext.cs

@ -0,0 +1,14 @@
using System.Reflection;
namespace Volo.Abp.Features
{
public class MethodInvocationFeatureCheckerContext
{
public MethodInfo Method { get; }
public MethodInvocationFeatureCheckerContext(MethodInfo method)
{
Method = method;
}
}
}

51
framework/src/Volo.Abp.Features/Volo/Abp/Features/MethodInvocationFeatureCheckerService.cs

@ -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();
}
}
}

33
framework/src/Volo.Abp.Features/Volo/Abp/Features/RequiresFeatureAttribute.cs

@ -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
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationException.cs → framework/src/Volo.Abp.Security/Volo/Abp/Authorization/AbpAuthorizationException.cs

23
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/ClassFeatureTestService.cs

@ -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()
{
}
}
}

2
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureChecker_Tests.cs

@ -5,7 +5,7 @@ using Xunit;
namespace Volo.Abp.Features
{
public class FeatureChecker_Tests : AbpFeaturesTestBase
public class FeatureChecker_Tests : FeatureTestBase
{
private readonly IFeatureChecker _featureChecker;
private readonly ICurrentTenant _currentTenant;

2
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureDefinitionManager_Tests.cs

@ -3,7 +3,7 @@ using Xunit;
namespace Volo.Abp.Features
{
public class FeatureDefinitionManager_Tests : AbpFeaturesTestBase
public class FeatureDefinitionManager_Tests : FeatureTestBase
{
private readonly IFeatureDefinitionManager _featureDefinitionManager;

76
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureInterceptor_Tests.cs

@ -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);
}
}
}

2
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/AbpFeaturesTestBase.cs → framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/FeatureTestBase.cs

@ -1,6 +1,6 @@
namespace Volo.Abp.Features
{
public class AbpFeaturesTestBase : AbpIntegratedTest<AbpFeaturesTestModule>
public class FeatureTestBase : AbpIntegratedTest<AbpFeaturesTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{

11
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/IMethodFeatureTestService.cs

@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace Volo.Abp.Features
{
public interface IMethodFeatureTestService
{
Task<int> Feature1Async();
Task NonFeatureAsync();
}
}

23
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/MethodFeatureTestService.cs

@ -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;
}
}
}

1
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureDefinitionProvider.cs

@ -6,6 +6,7 @@
{
var group = context.AddGroup("Test Group");
group.AddFeature("BooleanTestFeature1");
group.AddFeature("BooleanTestFeature2");
group.AddFeature("IntegerTestFeature1", defaultValue: "1");
}
}

8
framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/TestFeatureStore.cs

@ -8,8 +8,11 @@ namespace Volo.Abp.Features
{
public class TestFeatureStore : IFeatureStore, ISingletonDependency
{
public static Guid Tenant1Id = new Guid("f460fcf7-f944-469a-967b-3b2463323dfe");
public static Guid Tenant2Id = new Guid("e10428ad-4608-4c34-a304-6f82502156f2");
public const string Tenant1IdValue = "f460fcf7-f944-469a-967b-3b2463323dfe";
public const string Tenant2IdValue = "e10428ad-4608-4c34-a304-6f82502156f2";
public static Guid Tenant1Id = new Guid(Tenant1IdValue);
public static Guid Tenant2Id = new Guid(Tenant2IdValue);
private readonly List<SettingRecord> _settingRecords;
@ -18,6 +21,7 @@ namespace Volo.Abp.Features
_settingRecords = new List<SettingRecord>
{
new SettingRecord("BooleanTestFeature1", TenantFeatureValueProvider.ProviderName, Tenant1Id.ToString(), "true"),
new SettingRecord("BooleanTestFeature2", TenantFeatureValueProvider.ProviderName, Tenant1Id.ToString(), "true"),
new SettingRecord("IntegerTestFeature1", TenantFeatureValueProvider.ProviderName, Tenant2Id.ToString(), "34")
};
}

Loading…
Cancel
Save