mirror of https://github.com/abpframework/abp.git
104 changed files with 1949 additions and 218 deletions
|
After Width: | Height: | Size: 154 KiB |
@ -0,0 +1,13 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.FlagIconCss |
|||
{ |
|||
public class FlagIconCssStyleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/flag-icon-css/css/flag-icon.min.css"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc.Abstractions; |
|||
using Volo.Abp.Aspects; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Features; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Features |
|||
{ |
|||
public class AbpFeatureActionFilter : IAsyncActionFilter, ITransientDependency |
|||
{ |
|||
private readonly IMethodInvocationFeatureCheckerService _methodInvocationAuthorizationService; |
|||
|
|||
public AbpFeatureActionFilter(IMethodInvocationFeatureCheckerService methodInvocationAuthorizationService) |
|||
{ |
|||
_methodInvocationAuthorizationService = methodInvocationAuthorizationService; |
|||
} |
|||
|
|||
public async Task OnActionExecutionAsync( |
|||
ActionExecutingContext context, |
|||
ActionExecutionDelegate next) |
|||
{ |
|||
if (!context.ActionDescriptor.IsControllerAction()) |
|||
{ |
|||
await next(); |
|||
return; |
|||
} |
|||
|
|||
var methodInfo = context.ActionDescriptor.GetMethodInfo(); |
|||
|
|||
using (AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.FeatureChecking)) |
|||
{ |
|||
await _methodInvocationAuthorizationService.CheckAsync( |
|||
new MethodInvocationFeatureCheckerContext(methodInfo) |
|||
); |
|||
|
|||
await next(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,8 +1,6 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Authorization.Permissions |
|||
namespace Volo.Abp.Authorization.Permissions |
|||
{ |
|||
public interface IPermissionDefinitionProvider : ISingletonDependency |
|||
public interface IPermissionDefinitionProvider |
|||
{ |
|||
void Define(IPermissionDefinitionContext context); |
|||
} |
|||
|
|||
@ -1,12 +1,12 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Authorization.Permissions |
|||
{ |
|||
public interface IPermissionValueProvider : ISingletonDependency |
|||
public interface IPermissionValueProvider |
|||
{ |
|||
string Name { get; } |
|||
|
|||
Task<PermissionValueProviderGrantInfo> CheckAsync(PermissionValueCheckContext context); |
|||
//TODO: Rename to GetResult? (CheckAsync throws exception by naming convention)
|
|||
Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context); |
|||
} |
|||
} |
|||
@ -1,20 +0,0 @@ |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Authorization.Permissions |
|||
{ |
|||
public static class PermissionCheckerExtensions |
|||
{ |
|||
public static async Task<bool> IsGrantedAsync(this IPermissionChecker permissionChecker, string name) |
|||
{ |
|||
return (await permissionChecker.CheckAsync(name)).IsGranted; |
|||
} |
|||
|
|||
public static async Task<bool> IsGrantedAsync(this IPermissionChecker permissionChecker, ClaimsPrincipal principal, string name) |
|||
{ |
|||
return (await permissionChecker.CheckAsync(principal, name)).IsGranted; |
|||
} |
|||
|
|||
//TODO: Add sync extensions
|
|||
} |
|||
} |
|||
@ -1,6 +1,8 @@ |
|||
namespace Volo.Abp.Authorization.Permissions |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Authorization.Permissions |
|||
{ |
|||
public abstract class PermissionDefinitionProvider : IPermissionDefinitionProvider |
|||
public abstract class PermissionDefinitionProvider : IPermissionDefinitionProvider, ITransientDependency |
|||
{ |
|||
public abstract void Define(IPermissionDefinitionContext context); |
|||
} |
|||
|
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.Authorization.Permissions |
|||
{ |
|||
public enum PermissionGrantResult |
|||
{ |
|||
Undefined, |
|||
Granted, |
|||
Prohibited |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Threading |
|||
{ |
|||
public static class TaskCache |
|||
{ |
|||
public static Task<bool> TrueResult { get; } |
|||
public static Task<bool> FalseResult { get; } |
|||
|
|||
static TaskCache() |
|||
{ |
|||
TrueResult = Task.FromResult(true); |
|||
FalseResult = Task.FromResult(false); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<AssemblyName>Volo.Abp.Features</AssemblyName> |
|||
<PackageId>Volo.Abp.Features</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Localization.Abstractions\Volo.Abp.Localization.Abstractions.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.MultiTenancy.Abstractions\Volo.Abp.MultiTenancy.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,49 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpLocalizationAbstractionsModule), |
|||
typeof(AbpMultiTenancyAbstractionsModule) |
|||
)] |
|||
public class AbpFeaturesModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.OnRegistred(FeatureInterceptorRegistrar.RegisterIfNeeded); |
|||
AutoAddDefinitionProviders(context.Services); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.Configure<FeatureOptions>(options => |
|||
{ |
|||
options.ValueProviders.Add<DefaultValueFeatureValueProvider>(); |
|||
options.ValueProviders.Add<TenantFeatureValueProvider>(); |
|||
}); |
|||
} |
|||
|
|||
private static void AutoAddDefinitionProviders(IServiceCollection services) |
|||
{ |
|||
var definitionProviders = new List<Type>(); |
|||
|
|||
services.OnRegistred(context => |
|||
{ |
|||
if (typeof(IFeatureDefinitionProvider).IsAssignableFrom(context.ImplementationType)) |
|||
{ |
|||
definitionProviders.Add(context.ImplementationType); |
|||
} |
|||
}); |
|||
|
|||
services.Configure<FeatureOptions>(options => |
|||
{ |
|||
options.DefinitionProviders.AddIfNotContains(definitionProviders); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class DefaultValueFeatureValueProvider : FeatureValueProvider |
|||
{ |
|||
public const string ProviderName = "Default"; |
|||
|
|||
public override string Name => ProviderName; |
|||
|
|||
public DefaultValueFeatureValueProvider(IFeatureStore settingStore) |
|||
: base(settingStore) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public override Task<string> GetOrNullAsync(FeatureDefinition setting) |
|||
{ |
|||
return Task.FromResult(setting.DefaultValue); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
[AttributeUsage(AttributeTargets.Method)] |
|||
public class DisableFeatureCheckAttribute : Attribute |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureChecker : FeatureCheckerBase |
|||
{ |
|||
protected FeatureOptions Options { get; } |
|||
protected IServiceProvider ServiceProvider { get; } |
|||
protected IFeatureDefinitionManager FeatureDefinitionManager { get; } |
|||
protected List<IFeatureValueProvider> Providers => _providers.Value; |
|||
|
|||
private readonly Lazy<List<IFeatureValueProvider>> _providers; |
|||
|
|||
public FeatureChecker( |
|||
IOptions<FeatureOptions> options, |
|||
IServiceProvider serviceProvider, |
|||
IFeatureDefinitionManager featureDefinitionManager) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
FeatureDefinitionManager = featureDefinitionManager; |
|||
|
|||
Options = options.Value; |
|||
|
|||
_providers = new Lazy<List<IFeatureValueProvider>>( |
|||
() => Options |
|||
.ValueProviders |
|||
.Select(type => ServiceProvider.GetRequiredService(type) as IFeatureValueProvider) |
|||
.ToList(), |
|||
true |
|||
); |
|||
} |
|||
|
|||
public override async Task<string> GetOrNullAsync(string name) |
|||
{ |
|||
var featureDefinition = FeatureDefinitionManager.Get(name); |
|||
var providers = Enumerable |
|||
.Reverse(Providers); |
|||
|
|||
if (featureDefinition.AllowedProviders.Any()) |
|||
{ |
|||
providers = providers.Where(p => featureDefinition.AllowedProviders.Contains(p.Name)); |
|||
} |
|||
|
|||
return await GetOrNullValueFromProvidersAsync(providers, featureDefinition); |
|||
} |
|||
|
|||
protected virtual async Task<string> GetOrNullValueFromProvidersAsync( |
|||
IEnumerable<IFeatureValueProvider> providers, |
|||
FeatureDefinition feature) |
|||
{ |
|||
foreach (var provider in providers) |
|||
{ |
|||
var value = await provider.GetOrNullAsync(feature); |
|||
if (value != null) |
|||
{ |
|||
return value; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public abstract class FeatureCheckerBase : IFeatureChecker, ITransientDependency |
|||
{ |
|||
public abstract Task<string> GetOrNullAsync(string name); |
|||
|
|||
public virtual async Task<bool> IsEnabledAsync(string name) |
|||
{ |
|||
var value = await GetOrNullAsync(name); |
|||
if (value == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
return bool.Parse(value); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
throw new AbpException( |
|||
$"The value '{value}' for the feature '{name}' should be a boolean, but was not!", |
|||
ex |
|||
); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,143 @@ |
|||
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 |
|||
{ |
|||
public static class FeatureCheckerExtensions |
|||
{ |
|||
public static async Task<T> GetAsync<T>( |
|||
[NotNull] this IFeatureChecker featureChecker, |
|||
[NotNull] string name, |
|||
T defaultValue = default) |
|||
where T : struct |
|||
{ |
|||
Check.NotNull(featureChecker, nameof(featureChecker)); |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
var value = await featureChecker.GetOrNullAsync(name); |
|||
return value?.To<T>() ?? defaultValue; |
|||
} |
|||
|
|||
public static string GetOrNull( |
|||
[NotNull] this IFeatureChecker featureChecker, |
|||
[NotNull] string name) |
|||
{ |
|||
Check.NotNull(featureChecker, nameof(featureChecker)); |
|||
return AsyncHelper.RunSync(() => featureChecker.GetOrNullAsync(name)); |
|||
} |
|||
|
|||
public static T Get<T>( |
|||
[NotNull] this IFeatureChecker featureChecker, |
|||
[NotNull] string name, |
|||
T defaultValue = default) |
|||
where T : struct |
|||
{ |
|||
return AsyncHelper.RunSync(() => featureChecker.GetAsync(name, defaultValue)); |
|||
} |
|||
|
|||
public static bool IsEnabled( |
|||
[NotNull] this IFeatureChecker featureChecker, |
|||
[NotNull] string name) |
|||
{ |
|||
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)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureDefinition |
|||
{ |
|||
/// <summary>
|
|||
/// Unique name of the feature.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public string Name { get; } |
|||
|
|||
[NotNull] |
|||
public ILocalizableString DisplayName |
|||
{ |
|||
get => _displayName; |
|||
set => _displayName = Check.NotNull(value, nameof(value)); |
|||
} |
|||
private ILocalizableString _displayName; |
|||
|
|||
[CanBeNull] |
|||
public ILocalizableString Description { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Parent of this feature, if one exists.
|
|||
/// If set, this feature can be enabled only if the parent is enabled.
|
|||
/// </summary>
|
|||
public FeatureDefinition Parent { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// List of child features.
|
|||
/// </summary>
|
|||
public IReadOnlyList<FeatureDefinition> Children => _children.ToImmutableList(); |
|||
private readonly List<FeatureDefinition> _children; |
|||
|
|||
/// <summary>
|
|||
/// Default value of the feature.
|
|||
/// </summary>
|
|||
[CanBeNull] |
|||
public string DefaultValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Can clients see this feature and it's value.
|
|||
/// Default: true.
|
|||
/// </summary>
|
|||
public bool IsVisibleToClients { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// A list of allowed providers to get/set value of this feature.
|
|||
/// An empty list indicates that all providers are allowed.
|
|||
/// </summary>
|
|||
public List<string> AllowedProviders { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets/sets a key-value on the <see cref="Properties"/>.
|
|||
/// </summary>
|
|||
/// <param name="name">Name of the property</param>
|
|||
/// <returns>
|
|||
/// Returns the value in the <see cref="Properties"/> dictionary by given <see cref="name"/>.
|
|||
/// Returns null if given <see cref="name"/> is not present in the <see cref="Properties"/> dictionary.
|
|||
/// </returns>
|
|||
public object this[string name] |
|||
{ |
|||
get => Properties.GetOrDefault(name); |
|||
set => Properties[name] = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Can be used to get/set custom properties for this feature.
|
|||
/// </summary>
|
|||
[NotNull] |
|||
public Dictionary<string, object> Properties { get; } |
|||
|
|||
//TODO: Implement input type like old ABP!
|
|||
|
|||
public FeatureDefinition( |
|||
string name, |
|||
string defaultValue = null, |
|||
ILocalizableString displayName = null, |
|||
ILocalizableString description = null, |
|||
bool isVisibleToClients = true) |
|||
{ |
|||
Name = name; |
|||
DefaultValue = defaultValue; |
|||
IsVisibleToClients = isVisibleToClients; |
|||
DisplayName = displayName ?? new FixedLocalizableString(name); |
|||
Description = description; |
|||
|
|||
Properties = new Dictionary<string, object>(); |
|||
AllowedProviders = new List<string>(); |
|||
_children = new List<FeatureDefinition>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a property in the <see cref="Properties"/> dictionary.
|
|||
/// This is a shortcut for nested calls on this object.
|
|||
/// </summary>
|
|||
public virtual FeatureDefinition WithProperty(string key, object value) |
|||
{ |
|||
Properties[key] = value; |
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a property in the <see cref="Properties"/> dictionary.
|
|||
/// This is a shortcut for nested calls on this object.
|
|||
/// </summary>
|
|||
public virtual FeatureDefinition WithProviders(params string[] providers) |
|||
{ |
|||
if (!providers.IsNullOrEmpty()) |
|||
{ |
|||
AllowedProviders.AddRange(providers); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a child feature.
|
|||
/// </summary>
|
|||
/// <returns>Returns a newly created child feature</returns>
|
|||
public FeatureDefinition CreateChild( |
|||
string name, |
|||
string defaultValue = null, |
|||
ILocalizableString displayName = null, |
|||
ILocalizableString description = null, |
|||
bool isVisibleToClients = true) |
|||
{ |
|||
var feature = new FeatureDefinition( |
|||
name, |
|||
defaultValue, |
|||
displayName, |
|||
description, |
|||
isVisibleToClients) |
|||
{ |
|||
Parent = this |
|||
}; |
|||
|
|||
_children.Add(feature); |
|||
return feature; |
|||
} |
|||
|
|||
public void RemoveChild(string name) |
|||
{ |
|||
var featureToRemove = _children.FirstOrDefault(f => f.Name == name); |
|||
if (featureToRemove == null) |
|||
{ |
|||
throw new AbpException($"Could not find a feature named '{name}' in the Children of this feature '{Name}'."); |
|||
} |
|||
|
|||
featureToRemove.Parent = null; |
|||
_children.Remove(featureToRemove); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return $"[{nameof(FeatureDefinition)}: {Name}]"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureDefinitionContext : IFeatureDefinitionContext |
|||
{ |
|||
internal Dictionary<string, FeatureGroupDefinition> Groups { get; } |
|||
|
|||
public FeatureDefinitionContext() |
|||
{ |
|||
Groups = new Dictionary<string, FeatureGroupDefinition>(); |
|||
} |
|||
|
|||
public FeatureGroupDefinition AddGroup(string name, ILocalizableString displayName = null) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
if (Groups.ContainsKey(name)) |
|||
{ |
|||
throw new AbpException($"There is already an existing permission group with name: {name}"); |
|||
} |
|||
|
|||
return Groups[name] = new FeatureGroupDefinition(name, displayName); |
|||
} |
|||
|
|||
public FeatureGroupDefinition GetGroupOrNull(string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
if (!Groups.ContainsKey(name)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return Groups[name]; |
|||
} |
|||
|
|||
public void RemoveGroup(string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
if (!Groups.ContainsKey(name)) |
|||
{ |
|||
throw new AbpException($"Undefined feature group: '{name}'."); |
|||
} |
|||
|
|||
Groups.Remove(name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureDefinitionManager : IFeatureDefinitionManager, ISingletonDependency |
|||
{ |
|||
protected IDictionary<string, FeatureGroupDefinition> FeatureGroupDefinitions => _lazyFeatureGroupDefinitions.Value; |
|||
private readonly Lazy<Dictionary<string, FeatureGroupDefinition>> _lazyFeatureGroupDefinitions; |
|||
|
|||
protected IDictionary<string, FeatureDefinition> FeatureDefinitions => _lazyFeatureDefinitions.Value; |
|||
private readonly Lazy<Dictionary<string, FeatureDefinition>> _lazyFeatureDefinitions; |
|||
|
|||
protected FeatureOptions Options { get; } |
|||
|
|||
private readonly IServiceProvider _serviceProvider; |
|||
|
|||
public FeatureDefinitionManager( |
|||
IOptions<FeatureOptions> options, |
|||
IServiceProvider serviceProvider) |
|||
{ |
|||
_serviceProvider = serviceProvider; |
|||
Options = options.Value; |
|||
|
|||
_lazyFeatureDefinitions = new Lazy<Dictionary<string, FeatureDefinition>>( |
|||
CreateFeatureDefinitions, |
|||
isThreadSafe: true |
|||
); |
|||
|
|||
_lazyFeatureGroupDefinitions = new Lazy<Dictionary<string, FeatureGroupDefinition>>( |
|||
CreateFeatureGroupDefinitions, |
|||
isThreadSafe:true |
|||
); |
|||
} |
|||
|
|||
public virtual FeatureDefinition Get(string name) |
|||
{ |
|||
Check.NotNull(name, nameof(name)); |
|||
|
|||
var feature = GetOrNull(name); |
|||
|
|||
if (feature == null) |
|||
{ |
|||
throw new AbpException("Undefined feature: " + name); |
|||
} |
|||
|
|||
return feature; |
|||
} |
|||
|
|||
public virtual IReadOnlyList<FeatureDefinition> GetAll() |
|||
{ |
|||
return FeatureDefinitions.Values.ToImmutableList(); |
|||
} |
|||
|
|||
public virtual FeatureDefinition GetOrNull(string name) |
|||
{ |
|||
return FeatureDefinitions.GetOrDefault(name); |
|||
} |
|||
|
|||
protected virtual Dictionary<string, FeatureDefinition> CreateFeatureDefinitions() |
|||
{ |
|||
var features = new Dictionary<string, FeatureDefinition>(); |
|||
|
|||
foreach (var groupDefinition in FeatureGroupDefinitions.Values) |
|||
{ |
|||
foreach (var feature in groupDefinition.Features) |
|||
{ |
|||
AddFeatureToDictionaryRecursively(features, feature); |
|||
} |
|||
} |
|||
|
|||
return features; |
|||
} |
|||
|
|||
protected virtual void AddFeatureToDictionaryRecursively( |
|||
Dictionary<string, FeatureDefinition> features, |
|||
FeatureDefinition feature) |
|||
{ |
|||
if (features.ContainsKey(feature.Name)) |
|||
{ |
|||
throw new AbpException("Duplicate feature name: " + feature.Name); |
|||
} |
|||
|
|||
features[feature.Name] = feature; |
|||
|
|||
foreach (var child in feature.Children) |
|||
{ |
|||
AddFeatureToDictionaryRecursively(features, child); |
|||
} |
|||
} |
|||
|
|||
protected virtual Dictionary<string, FeatureGroupDefinition> CreateFeatureGroupDefinitions() |
|||
{ |
|||
var context = new FeatureDefinitionContext(); |
|||
|
|||
using (var scope = _serviceProvider.CreateScope()) |
|||
{ |
|||
var providers = Options |
|||
.DefinitionProviders |
|||
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IFeatureDefinitionProvider) |
|||
.ToList(); |
|||
|
|||
foreach (var provider in providers) |
|||
{ |
|||
provider.Define(context); |
|||
} |
|||
} |
|||
|
|||
return context.Groups; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public abstract class FeatureDefinitionProvider : IFeatureDefinitionProvider, ISingletonDependency |
|||
{ |
|||
public abstract void Define(IFeatureDefinitionContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureGroupDefinition |
|||
{ |
|||
/// <summary>
|
|||
/// Unique name of the group.
|
|||
/// </summary>
|
|||
public string Name { get; } |
|||
|
|||
public Dictionary<string, object> Properties { get; } |
|||
|
|||
public ILocalizableString DisplayName |
|||
{ |
|||
get => _displayName; |
|||
set => _displayName = Check.NotNull(value, nameof(value)); |
|||
} |
|||
private ILocalizableString _displayName; |
|||
|
|||
public IReadOnlyList<FeatureDefinition> Features => _features.ToImmutableList(); |
|||
private readonly List<FeatureDefinition> _features; |
|||
|
|||
/// <summary>
|
|||
/// Gets/sets a key-value on the <see cref="Properties"/>.
|
|||
/// </summary>
|
|||
/// <param name="name">Name of the property</param>
|
|||
/// <returns>
|
|||
/// Returns the value in the <see cref="Properties"/> dictionary by given <see cref="name"/>.
|
|||
/// Returns null if given <see cref="name"/> is not present in the <see cref="Properties"/> dictionary.
|
|||
/// </returns>
|
|||
public object this[string name] |
|||
{ |
|||
get => Properties.GetOrDefault(name); |
|||
set => Properties[name] = value; |
|||
} |
|||
|
|||
protected internal FeatureGroupDefinition( |
|||
string name, |
|||
ILocalizableString displayName = null) |
|||
{ |
|||
Name = name; |
|||
DisplayName = displayName ?? new FixedLocalizableString(Name); |
|||
|
|||
Properties = new Dictionary<string, object>(); |
|||
_features = new List<FeatureDefinition>(); |
|||
} |
|||
|
|||
public virtual FeatureDefinition AddFeature( |
|||
string name, |
|||
string defaultValue = null, |
|||
ILocalizableString displayName = null, |
|||
ILocalizableString description = null, |
|||
bool isVisibleToClients = true) |
|||
{ |
|||
var feature = new FeatureDefinition( |
|||
name, |
|||
defaultValue, |
|||
displayName, |
|||
description, |
|||
isVisibleToClients |
|||
); |
|||
|
|||
_features.Add(feature); |
|||
|
|||
return feature; |
|||
} |
|||
|
|||
public virtual List<FeatureDefinition> GetFeaturesWithChildren() |
|||
{ |
|||
var features = new List<FeatureDefinition>(); |
|||
|
|||
foreach (var feature in _features) |
|||
{ |
|||
AddFeatureToListRecursively(features, feature); |
|||
} |
|||
|
|||
return features; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets a property in the <see cref="Properties"/> dictionary.
|
|||
/// This is a shortcut for nested calls on this object.
|
|||
/// </summary>
|
|||
public virtual FeatureGroupDefinition WithProperty(string key, object value) |
|||
{ |
|||
Properties[key] = value; |
|||
return this; |
|||
} |
|||
|
|||
private void AddFeatureToListRecursively(List<FeatureDefinition> features, FeatureDefinition feature) |
|||
{ |
|||
features.Add(feature); |
|||
|
|||
foreach (var child in feature.Children) |
|||
{ |
|||
AddFeatureToListRecursively(features, child); |
|||
} |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return $"[{nameof(FeatureGroupDefinition)} {Name}]"; |
|||
} |
|||
} |
|||
} |
|||
@ -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,17 @@ |
|||
using Volo.Abp.Collections; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureOptions |
|||
{ |
|||
public ITypeList<IFeatureDefinitionProvider> DefinitionProviders { get; } |
|||
|
|||
public ITypeList<IFeatureValueProvider> ValueProviders { get; } |
|||
|
|||
public FeatureOptions() |
|||
{ |
|||
DefinitionProviders = new TypeList<IFeatureDefinitionProvider>(); |
|||
ValueProviders = new TypeList<IFeatureValueProvider>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
[Serializable] |
|||
public class FeatureValue : NameValue |
|||
{ |
|||
public FeatureValue() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public FeatureValue(string name, string value) |
|||
{ |
|||
Name = name; |
|||
Value = value; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public abstract class FeatureValueProvider : IFeatureValueProvider, ISingletonDependency |
|||
{ |
|||
public abstract string Name { get; } |
|||
|
|||
protected IFeatureStore FeatureStore { get; } |
|||
|
|||
protected FeatureValueProvider(IFeatureStore featureStore) |
|||
{ |
|||
FeatureStore = featureStore; |
|||
} |
|||
|
|||
public abstract Task<string> GetOrNullAsync(FeatureDefinition feature); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using JetBrains.Annotations; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureChecker |
|||
{ |
|||
Task<string> GetOrNullAsync([NotNull] string name); |
|||
|
|||
Task<bool> IsEnabledAsync(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureDefinitionContext |
|||
{ |
|||
FeatureGroupDefinition AddGroup([NotNull] string name, ILocalizableString displayName = null); |
|||
|
|||
FeatureGroupDefinition GetGroupOrNull(string name); |
|||
|
|||
void RemoveGroup(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.Collections.Generic; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureDefinitionManager |
|||
{ |
|||
[NotNull] |
|||
FeatureDefinition Get([NotNull] string name); |
|||
|
|||
IReadOnlyList<FeatureDefinition> GetAll(); |
|||
|
|||
FeatureDefinition GetOrNull(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureDefinitionProvider |
|||
{ |
|||
void Define(IFeatureDefinitionContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureStore |
|||
{ |
|||
Task<string> GetOrNullAsync( |
|||
[NotNull] string name, |
|||
[CanBeNull] string providerName, |
|||
[CanBeNull] string providerKey |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public interface IFeatureValueProvider |
|||
{ |
|||
string Name { get; } |
|||
|
|||
Task<string> GetOrNullAsync([NotNull] FeatureDefinition feature); |
|||
} |
|||
} |
|||
@ -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,23 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
[Dependency(TryRegister = true)] |
|||
public class NullFeatureStore : IFeatureStore, ISingletonDependency |
|||
{ |
|||
public ILogger<NullFeatureStore> Logger { get; set; } |
|||
|
|||
public NullFeatureStore() |
|||
{ |
|||
Logger = NullLogger<NullFeatureStore>.Instance; |
|||
} |
|||
|
|||
public Task<string> GetOrNullAsync(string name, string providerName, string providerKey) |
|||
{ |
|||
return Task.FromResult((string) null); |
|||
} |
|||
} |
|||
} |
|||
@ -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,25 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class TenantFeatureValueProvider : FeatureValueProvider |
|||
{ |
|||
public const string ProviderName = "Tenant"; |
|||
|
|||
public override string Name => ProviderName; |
|||
|
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
|
|||
public TenantFeatureValueProvider(IFeatureStore featureStore, ICurrentTenant currentTenant) |
|||
: base(featureStore) |
|||
{ |
|||
CurrentTenant = currentTenant; |
|||
} |
|||
|
|||
public override async Task<string> GetOrNullAsync(FeatureDefinition feature) |
|||
{ |
|||
return await FeatureStore.GetOrNullAsync(feature.Name, Name, CurrentTenant.Id?.ToString()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Features; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Features |
|||
{ |
|||
public class FakeFeatureChecker : FeatureCheckerBase |
|||
{ |
|||
public override Task<string> GetOrNullAsync(string name) |
|||
{ |
|||
return Task.FromResult(GetOrNull(name)); |
|||
} |
|||
|
|||
private static string GetOrNull(string name) |
|||
{ |
|||
switch (name) |
|||
{ |
|||
case "AllowedFeature": |
|||
return true.ToString(); |
|||
case "NotAllowedFeature": |
|||
return null; //or false, doesn't matter
|
|||
} |
|||
|
|||
throw new ApplicationException($"Unknown feature: '{name}'"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.Features; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Features |
|||
{ |
|||
[Route("api/feature-test")] |
|||
public class FeatureTestController : AbpController |
|||
{ |
|||
[HttpGet] |
|||
[Route("allowed-feature")] |
|||
[RequiresFeature("AllowedFeature")] |
|||
public Task AllowedFeatureAsync() |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("not-allowed-feature")] |
|||
[RequiresFeature("NotAllowedFeature")] |
|||
public void NotAllowedFeature() |
|||
{ |
|||
|
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("no-feature")] |
|||
public int NoFeature() |
|||
{ |
|||
return 42; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System.Net; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Features |
|||
{ |
|||
public class FeatureTestController_Tests : AspNetCoreMvcTestBase |
|||
{ |
|||
[Fact] |
|||
public async Task Should_Allow_Enabled_Features() |
|||
{ |
|||
await GetResponseAsStringAsync( |
|||
"/api/feature-test/allowed-feature" |
|||
); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Allow_Not_Enabled_Features() |
|||
{ |
|||
await GetResponseAsStringAsync( |
|||
"/api/feature-test/not-allowed-feature", |
|||
HttpStatusCode.Unauthorized |
|||
); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Allow_Actions_With_No_Feature() |
|||
{ |
|||
await GetResponseAsStringAsync( |
|||
"/api/feature-test/no-feature" |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netcoreapp2.2</TargetFramework> |
|||
<LangVersion>latest</LangVersion> |
|||
<AssemblyName>Volo.Abp.Features.Tests</AssemblyName> |
|||
<PackageId>Volo.Abp.Features.Tests</PackageId> |
|||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
|||
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Features\Volo.Abp.Features.csproj" /> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,18 @@ |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpFeaturesModule), |
|||
typeof(AbpTestBaseModule), |
|||
typeof(AbpAutofacModule) |
|||
)] |
|||
public class AbpFeaturesTestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -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,53 @@ |
|||
using Shouldly; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureChecker_Tests : FeatureTestBase |
|||
{ |
|||
private readonly IFeatureChecker _featureChecker; |
|||
private readonly ICurrentTenant _currentTenant; |
|||
|
|||
public FeatureChecker_Tests() |
|||
{ |
|||
_featureChecker = GetRequiredService<IFeatureChecker>(); |
|||
_currentTenant = GetRequiredService<ICurrentTenant>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task IsEnabledAsync() |
|||
{ |
|||
//Tenant is unknown
|
|||
(await _featureChecker.IsEnabledAsync("BooleanTestFeature1")).ShouldBeFalse(); |
|||
|
|||
using (_currentTenant.Change(TestFeatureStore.Tenant1Id)) |
|||
{ |
|||
(await _featureChecker.IsEnabledAsync("BooleanTestFeature1")).ShouldBeTrue(); |
|||
} |
|||
|
|||
using (_currentTenant.Change(TestFeatureStore.Tenant2Id)) |
|||
{ |
|||
(await _featureChecker.IsEnabledAsync("BooleanTestFeature1")).ShouldBeFalse(); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task GetOrNullAsync() |
|||
{ |
|||
//Tenant is unknown
|
|||
(await _featureChecker.GetOrNullAsync("IntegerTestFeature1")).ShouldBe("1"); |
|||
|
|||
using (_currentTenant.Change(TestFeatureStore.Tenant1Id)) |
|||
{ |
|||
(await _featureChecker.GetOrNullAsync("IntegerTestFeature1")).ShouldBe("1"); |
|||
} |
|||
|
|||
using (_currentTenant.Change(TestFeatureStore.Tenant2Id)) |
|||
{ |
|||
(await _featureChecker.GetOrNullAsync("IntegerTestFeature1")).ShouldBe("34"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using Shouldly; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureDefinitionManager_Tests : FeatureTestBase |
|||
{ |
|||
private readonly IFeatureDefinitionManager _featureDefinitionManager; |
|||
|
|||
public FeatureDefinitionManager_Tests() |
|||
{ |
|||
_featureDefinitionManager = GetRequiredService<IFeatureDefinitionManager>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Get_Defined_Features() |
|||
{ |
|||
_featureDefinitionManager.GetOrNull("BooleanTestFeature1").ShouldNotBeNull(); |
|||
_featureDefinitionManager.Get("BooleanTestFeature1").Name.ShouldBe("BooleanTestFeature1"); |
|||
|
|||
_featureDefinitionManager.GetOrNull("IntegerTestFeature1").ShouldNotBeNull(); |
|||
_featureDefinitionManager.Get("IntegerTestFeature1").Name.ShouldBe("IntegerTestFeature1"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Get_Undefined_Features() |
|||
{ |
|||
_featureDefinitionManager.GetOrNull("UndefinedFeature").ShouldBeNull(); |
|||
Assert.Throws<AbpException>(() => |
|||
{ |
|||
_featureDefinitionManager.Get("UndefinedFeature"); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class FeatureTestBase : AbpIntegratedTest<AbpFeaturesTestModule> |
|||
{ |
|||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
|||
{ |
|||
options.UseAutofac(); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class TestFeatureDefinitionProvider : FeatureDefinitionProvider |
|||
{ |
|||
public override void Define(IFeatureDefinitionContext context) |
|||
{ |
|||
var group = context.AddGroup("Test Group"); |
|||
group.AddFeature("BooleanTestFeature1"); |
|||
group.AddFeature("BooleanTestFeature2"); |
|||
group.AddFeature("IntegerTestFeature1", defaultValue: "1"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Features |
|||
{ |
|||
public class TestFeatureStore : IFeatureStore, ISingletonDependency |
|||
{ |
|||
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; |
|||
|
|||
public TestFeatureStore() |
|||
{ |
|||
_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") |
|||
}; |
|||
} |
|||
|
|||
public Task<string> GetOrNullAsync(string name, string providerName, string providerKey) |
|||
{ |
|||
return Task.FromResult( |
|||
_settingRecords.FirstOrDefault(sr => |
|||
sr.Name == name && |
|||
sr.ProviderName == providerName && |
|||
sr.ProviderKey == providerKey |
|||
)?.Value |
|||
); |
|||
} |
|||
|
|||
private class SettingRecord |
|||
{ |
|||
public string Name { get; } |
|||
public string ProviderName { get; } |
|||
public string ProviderKey { get; } |
|||
public string Value { get; } |
|||
|
|||
public SettingRecord(string name, string providerName, string providerKey, string value) |
|||
{ |
|||
Name = name; |
|||
ProviderName = providerName; |
|||
ProviderKey = providerKey; |
|||
Value = value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,8 +1,8 @@ |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Authorization.Permissions |
|||
namespace Volo.Abp.PermissionManagement |
|||
{ |
|||
public class PermissionValueProviderGrantInfo |
|||
public class PermissionValueProviderGrantInfo //TODO: Rename to PermissionGrantInfo
|
|||
{ |
|||
public static PermissionValueProviderGrantInfo NonGranted { get; } = new PermissionValueProviderGrantInfo(false); |
|||
|
|||
@ -0,0 +1,6 @@ |
|||
module.exports = { |
|||
mappings: { |
|||
"@node_modules/flag-icon-css/css/*": "@libs/flag-icon-css/css", |
|||
"@node_modules/flag-icon-css/flags/1x1/*": "@libs/flag-icon-css/flags/1x1" |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue