pull/859/head
Yunus Emre Kalkan 7 years ago
commit 55f2495d5e

@ -24,7 +24,7 @@ This sample aims to demonstrate a simple yet complete microservice solution;
The diagram below shows the system:
![microservice-sample-diagram](../images/microservice-sample-diagram.png)
![microservice-sample-diagram-2](../images/microservice-sample-diagram-2.png)
### Source Code
@ -32,7 +32,7 @@ You can get the source code from [the GitHub repository](https://github.com/abpf
### Status
This sample is still in development, not completed yet.
Initial version of this sample has been completed. Additional improvement are still in development.
## Running the Solution
@ -50,6 +50,20 @@ Running as docker containers is easier since all dependencies are pre-configured
- Open a command line in the `samples/MicroserviceDemo` folder of the repository.
- Pull images from Docker Hub:
```
docker-compose -f docker-compose.yml -f docker-compose.migrations.yml pull
```
- If you want to build images locally you may skip the above step and instead use build command:
```
docker-compose -f docker-compose.yml -f docker-compose.migrations.yml build
```
Building images may take a **long time** depending on your machine.
- Restore SQL Server databases:
```
@ -62,8 +76,6 @@ Running as docker containers is easier since all dependencies are pre-configured
docker-compose up -d
```
At the first run, it will take a **long time** because it will build all docker images.
- Add this line to the end of your `hosts` file:
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

@ -222,9 +222,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.Identi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.IdentityModel", "src\Volo.Abp.IdentityModel\Volo.Abp.IdentityModel.csproj", "{64D99E19-EE25-465A-82E5-17B25F4C4E18}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.Client", "src\Volo.Abp.AspNetCore.Mvc.Client\Volo.Abp.AspNetCore.Mvc.Client.csproj", "{E803DDB8-81EA-454B-9A66-9C2941100B67}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.Client", "src\Volo.Abp.AspNetCore.Mvc.Client\Volo.Abp.AspNetCore.Mvc.Client.csproj", "{E803DDB8-81EA-454B-9A66-9C2941100B67}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.Contracts", "src\Volo.Abp.AspNetCore.Mvc.Contracts\Volo.Abp.AspNetCore.Mvc.Contracts.csproj", "{88F6D091-CA16-4B71-9499-8D5B8FA2E712}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.Contracts", "src\Volo.Abp.AspNetCore.Mvc.Contracts\Volo.Abp.AspNetCore.Mvc.Contracts.csproj", "{88F6D091-CA16-4B71-9499-8D5B8FA2E712}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Features", "src\Volo.Abp.Features\Volo.Abp.Features.csproj", "{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Features.Tests", "test\Volo.Abp.Features.Tests\Volo.Abp.Features.Tests.csproj", "{575BEFA1-19C2-49B1-8D31-B5D4472328DE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -640,6 +644,14 @@ Global
{88F6D091-CA16-4B71-9499-8D5B8FA2E712}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88F6D091-CA16-4B71-9499-8D5B8FA2E712}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88F6D091-CA16-4B71-9499-8D5B8FA2E712}.Release|Any CPU.Build.0 = Release|Any CPU
{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E}.Release|Any CPU.Build.0 = Release|Any CPU
{575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -749,6 +761,8 @@ Global
{64D99E19-EE25-465A-82E5-17B25F4C4E18} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{E803DDB8-81EA-454B-9A66-9C2941100B67} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{88F6D091-CA16-4B71-9499-8D5B8FA2E712} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{01E3D389-8872-4EB1-9D3D-13B6ED54DE0E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{575BEFA1-19C2-49B1-8D31-B5D4472328DE} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

@ -14,19 +14,17 @@ namespace Volo.Abp.AspNetCore.Mvc.Client
ConfigurationClient = configurationClient;
}
public async Task<PermissionGrantInfo> CheckAsync(string name)
public async Task<bool> IsGrantedAsync(string name)
{
var configuration = await ConfigurationClient.GetAsync();
return new PermissionGrantInfo(
name,
configuration.Auth.GrantedPolicies.ContainsKey(name)
);
return configuration.Auth.GrantedPolicies.ContainsKey(name);
}
public Task<PermissionGrantInfo> CheckAsync(ClaimsPrincipal claimsPrincipal, string name)
public Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name)
{
return CheckAsync(name);
/* This provider always works for the current principal. */
return IsGrantedAsync(name);
}
}
}

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

@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Auditing;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.Features;
using Volo.Abp.AspNetCore.Mvc.Uow;
using Volo.Abp.AspNetCore.Mvc.Validation;
@ -26,6 +27,7 @@ namespace Volo.Abp.AspNetCore.Mvc
private static void AddFilters(MvcOptions options)
{
options.Filters.AddService(typeof(AbpAuditActionFilter));
options.Filters.AddService(typeof(AbpFeatureActionFilter));
options.Filters.AddService(typeof(AbpValidationActionFilter));
options.Filters.AddService(typeof(AbpUowActionFilter));
options.Filters.AddService(typeof(AbpExceptionFilter));

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

@ -12,7 +12,6 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow
{
public class AbpUowActionFilter : IAsyncActionFilter, ITransientDependency
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly UnitOfWorkDefaultOptions _defaultOptions;

@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
@ -16,6 +18,7 @@ namespace Volo.Abp.Authorization
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.OnRegistred(AuthorizationInterceptorRegistrar.RegisterIfNeeded);
AutoAddDefinitionProviders(context.Services);
}
public override void ConfigureServices(ServiceConfigurationContext context)
@ -31,5 +34,23 @@ namespace Volo.Abp.Authorization
options.ValueProviders.Add<ClientPermissionValueProvider>();
});
}
private static void AutoAddDefinitionProviders(IServiceCollection services)
{
var definitionProviders = new List<Type>();
services.OnRegistred(context =>
{
if (typeof(IPermissionDefinitionProvider).IsAssignableFrom(context.ImplementationType))
{
definitionProviders.Add(context.ImplementationType);
}
});
services.Configure<PermissionOptions>(options =>
{
options.DefinitionProviders.AddIfNotContains(definitionProviders);
});
}
}
}

@ -1,5 +1,6 @@
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp.Threading;
namespace Volo.Abp.Authorization.Permissions
{
@ -11,14 +12,14 @@ namespace Volo.Abp.Authorization.Permissions
/// </summary>
public class AlwaysAllowPermissionChecker : IPermissionChecker
{
public Task<PermissionGrantInfo> CheckAsync(string name)
public Task<bool> IsGrantedAsync(string name)
{
return Task.FromResult(new PermissionGrantInfo(name, true, "AlwaysAllow"));
return TaskCache.TrueResult;
}
public Task<PermissionGrantInfo> CheckAsync(ClaimsPrincipal claimsPrincipal, string name)
public Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name)
{
return Task.FromResult(new PermissionGrantInfo(name, true, "AlwaysAllow"));
return TaskCache.TrueResult;
}
}
}

@ -15,21 +15,18 @@ namespace Volo.Abp.Authorization.Permissions
}
public override async Task<PermissionValueProviderGrantInfo> CheckAsync(PermissionValueCheckContext context)
public override async Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context)
{
var clientId = context.Principal?.FindFirst(AbpClaimTypes.ClientId)?.Value;
if (clientId == null)
{
return PermissionValueProviderGrantInfo.NonGranted;
return PermissionGrantResult.Undefined;
}
if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, clientId))
{
return new PermissionValueProviderGrantInfo(true, clientId);
}
return PermissionValueProviderGrantInfo.NonGranted;
return await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, clientId)
? PermissionGrantResult.Granted
: PermissionGrantResult.Undefined;
}
}
}

@ -6,8 +6,8 @@ namespace Volo.Abp.Authorization.Permissions
{
public interface IPermissionChecker
{
Task<PermissionGrantInfo> CheckAsync([NotNull]string name);
Task<bool> IsGrantedAsync([NotNull]string name);
Task<PermissionGrantInfo> CheckAsync([CanBeNull] ClaimsPrincipal claimsPrincipal, [NotNull]string name);
Task<bool> IsGrantedAsync([CanBeNull] ClaimsPrincipal claimsPrincipal, [NotNull]string name);
}
}

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

@ -2,6 +2,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.Authorization.Permissions
{
@ -16,7 +17,7 @@ namespace Volo.Abp.Authorization.Permissions
public Task<bool> IsGrantedAsync(string name, string providerName, string providerKey)
{
return Task.FromResult(false);
return TaskCache.FalseResult;
}
}
}

@ -41,12 +41,12 @@ namespace Volo.Abp.Authorization.Permissions
);
}
public virtual Task<PermissionGrantInfo> CheckAsync(string name)
public virtual Task<bool> IsGrantedAsync(string name)
{
return CheckAsync(PrincipalAccessor.Principal, name);
return IsGrantedAsync(PrincipalAccessor.Principal, name);
}
public virtual async Task<PermissionGrantInfo> CheckAsync(ClaimsPrincipal claimsPrincipal, string name)
public virtual async Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name)
{
Check.NotNull(name, nameof(name));
@ -55,6 +55,8 @@ namespace Volo.Abp.Authorization.Permissions
claimsPrincipal
);
var isGranted = false;
foreach (var provider in ValueProviders)
{
if (context.Permission.Providers.Any() &&
@ -64,13 +66,18 @@ namespace Volo.Abp.Authorization.Permissions
}
var result = await provider.CheckAsync(context);
if (result.IsGranted)
if (result == PermissionGrantResult.Granted)
{
isGranted = true;
}
else if (result == PermissionGrantResult.Prohibited)
{
return new PermissionGrantInfo(context.Permission.Name, true, provider.Name, result.ProviderKey);
return false;
}
}
return new PermissionGrantInfo(context.Permission.Name, false);
return isGranted;
}
}
}

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

@ -22,7 +22,7 @@ namespace Volo.Abp.Authorization.Permissions
/// A list of allowed providers to get/set value of this permission.
/// An empty list indicates that all providers are allowed.
/// </summary>
public List<string> Providers { get; }
public List<string> Providers { get; } //TODO: Rename to AllowedProviders?
public ILocalizableString DisplayName
{
@ -53,7 +53,9 @@ namespace Volo.Abp.Authorization.Permissions
set => Properties[name] = value;
}
protected internal PermissionDefinition([NotNull] string name, ILocalizableString displayName = null)
protected internal PermissionDefinition(
[NotNull] string name,
ILocalizableString displayName = null)
{
Name = Check.NotNull(name, nameof(name));
DisplayName = displayName ?? new FixedLocalizableString(name);
@ -63,7 +65,9 @@ namespace Volo.Abp.Authorization.Permissions
_children = new List<PermissionDefinition>();
}
public virtual PermissionDefinition AddChild([NotNull] string name, ILocalizableString displayName = null)
public virtual PermissionDefinition AddChild(
[NotNull] string name,
ILocalizableString displayName = null)
{
var child = new PermissionDefinition(name, displayName)
{

@ -10,9 +10,6 @@ namespace Volo.Abp.Authorization.Permissions
{
public class PermissionDefinitionManager : IPermissionDefinitionManager, ISingletonDependency
{
protected List<IPermissionDefinitionProvider> Providers => _lazyProviders.Value;
private readonly Lazy<List<IPermissionDefinitionProvider>> _lazyProviders;
protected IDictionary<string, PermissionGroupDefinition> PermissionGroupDefinitions => _lazyPermissionGroupDefinitions.Value;
private readonly Lazy<Dictionary<string, PermissionGroupDefinition>> _lazyPermissionGroupDefinitions;
@ -30,9 +27,15 @@ namespace Volo.Abp.Authorization.Permissions
_serviceProvider = serviceProvider;
Options = options.Value;
_lazyProviders = new Lazy<List<IPermissionDefinitionProvider>>(CreatePermissionProviders, true);
_lazyPermissionDefinitions = new Lazy<Dictionary<string, PermissionDefinition>>(CreatePermissionDefinitions, true);
_lazyPermissionGroupDefinitions = new Lazy<Dictionary<string, PermissionGroupDefinition>>(CreatePermissionGroupDefinitions, true);
_lazyPermissionDefinitions = new Lazy<Dictionary<string, PermissionDefinition>>(
CreatePermissionDefinitions,
isThreadSafe: true
);
_lazyPermissionGroupDefinitions = new Lazy<Dictionary<string, PermissionGroupDefinition>>(
CreatePermissionGroupDefinitions,
isThreadSafe: true
);
}
public virtual PermissionDefinition Get(string name)
@ -64,14 +67,6 @@ namespace Volo.Abp.Authorization.Permissions
return PermissionGroupDefinitions.Values.ToImmutableList();
}
protected virtual List<IPermissionDefinitionProvider> CreatePermissionProviders()
{
return Options
.DefinitionProviders
.Select(p => _serviceProvider.GetRequiredService(p) as IPermissionDefinitionProvider)
.ToList();
}
protected virtual Dictionary<string, PermissionDefinition> CreatePermissionDefinitions()
{
var permissions = new Dictionary<string, PermissionDefinition>();
@ -87,7 +82,9 @@ namespace Volo.Abp.Authorization.Permissions
return permissions;
}
protected virtual void AddPermissionToDictionaryRecursively(Dictionary<string, PermissionDefinition> permissions, PermissionDefinition permission)
protected virtual void AddPermissionToDictionaryRecursively(
Dictionary<string, PermissionDefinition> permissions,
PermissionDefinition permission)
{
if (permissions.ContainsKey(permission.Name))
{
@ -106,9 +103,17 @@ namespace Volo.Abp.Authorization.Permissions
{
var context = new PermissionDefinitionContext();
foreach (var provider in Providers)
using (var scope = _serviceProvider.CreateScope())
{
provider.Define(context);
var providers = Options
.DefinitionProviders
.Select(p => scope.ServiceProvider.GetRequiredService(p) as IPermissionDefinitionProvider)
.ToList();
foreach (var provider in providers)
{
provider.Define(context);
}
}
return context.Groups;

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

@ -37,7 +37,9 @@ namespace Volo.Abp.Authorization.Permissions
set => Properties[name] = value;
}
protected internal PermissionGroupDefinition(string name, ILocalizableString displayName = null)
protected internal PermissionGroupDefinition(
string name,
ILocalizableString displayName = null)
{
Name = name;
DisplayName = displayName ?? new FixedLocalizableString(Name);
@ -46,7 +48,9 @@ namespace Volo.Abp.Authorization.Permissions
_permissions = new List<PermissionDefinition>();
}
public virtual PermissionDefinition AddPermission(string name, ILocalizableString displayName = null)
public virtual PermissionDefinition AddPermission(
string name,
ILocalizableString displayName = null)
{
var permission = new PermissionDefinition(name, displayName);

@ -11,7 +11,9 @@ namespace Volo.Abp.Authorization.Permissions
[CanBeNull]
public ClaimsPrincipal Principal { get; }
public PermissionValueCheckContext([NotNull] PermissionDefinition permission, [CanBeNull] ClaimsPrincipal principal)
public PermissionValueCheckContext(
[NotNull] PermissionDefinition permission,
[CanBeNull] ClaimsPrincipal principal)
{
Check.NotNull(permission, nameof(permission));

@ -1,8 +1,9 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Authorization.Permissions
{
public abstract class PermissionValueProvider : IPermissionValueProvider
public abstract class PermissionValueProvider : IPermissionValueProvider, ISingletonDependency
{
public abstract string Name { get; }
@ -13,6 +14,6 @@ namespace Volo.Abp.Authorization.Permissions
PermissionStore = permissionStore;
}
public abstract Task<PermissionValueProviderGrantInfo> CheckAsync(PermissionValueCheckContext context);
public abstract Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context);
}
}

@ -16,23 +16,23 @@ namespace Volo.Abp.Authorization.Permissions
}
public override async Task<PermissionValueProviderGrantInfo> CheckAsync(PermissionValueCheckContext context)
public override async Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context)
{
var roles = context.Principal?.FindAll(AbpClaimTypes.Role).Select(c => c.Value).ToArray();
if (roles == null || !roles.Any())
{
return PermissionValueProviderGrantInfo.NonGranted;
return PermissionGrantResult.Undefined;
}
foreach (var role in roles)
{
if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, role))
{
return new PermissionValueProviderGrantInfo(true, role);
return PermissionGrantResult.Granted;
}
}
return PermissionValueProviderGrantInfo.NonGranted;
return PermissionGrantResult.Undefined;
}
}
}

@ -15,21 +15,18 @@ namespace Volo.Abp.Authorization.Permissions
}
public override async Task<PermissionValueProviderGrantInfo> CheckAsync(PermissionValueCheckContext context)
public override async Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context)
{
var userId = context.Principal?.FindFirst(AbpClaimTypes.UserId)?.Value;
if (userId == null)
{
return PermissionValueProviderGrantInfo.NonGranted;
return PermissionGrantResult.Undefined;
}
if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, userId))
{
return new PermissionValueProviderGrantInfo(true, userId);
}
return PermissionValueProviderGrantInfo.NonGranted;
return await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, userId)
? PermissionGrantResult.Granted
: PermissionGrantResult.Undefined;
}
}
}

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

@ -8,17 +8,11 @@ namespace Volo.Abp
[Serializable]
public class NameValue : NameValue<string>
{
/// <summary>
/// Creates a new <see cref="NameValue"/>.
/// </summary>
public NameValue()
{
}
/// <summary>
/// Creates a new <see cref="NameValue"/>.
/// </summary>
public NameValue(string name, string value)
{
Name = name;
@ -42,17 +36,11 @@ namespace Volo.Abp
/// </summary>
public T Value { get; set; }
/// <summary>
/// Creates a new <see cref="NameValue"/>.
/// </summary>
public NameValue()
{
}
/// <summary>
/// Creates a new <see cref="NameValue"/>.
/// </summary>
public NameValue(string name, T value)
{
Name = name;

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

@ -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" />

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

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

@ -16,9 +16,9 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.EntityFrameworkCore\Volo.Abp.EntityFrameworkCore.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MySql.Data.EntityFrameworkCore" Version="8.0.13" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.2.0" />
</ItemGroup>
</Project>

@ -1,7 +1,7 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using MySql.Data.EntityFrameworkCore.Infraestructure;
using System;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
namespace Volo.Abp.EntityFrameworkCore
@ -10,15 +10,15 @@ namespace Volo.Abp.EntityFrameworkCore
{
public static DbContextOptionsBuilder UseMySQL(
[NotNull] this AbpDbContextConfigurationContext context,
[CanBeNull] Action<MySQLDbContextOptionsBuilder> mySQLOptionsAction = null)
[CanBeNull] Action<MySqlDbContextOptionsBuilder> mySQLOptionsAction = null)
{
if (context.ExistingConnection != null)
{
return context.DbContextOptions.UseMySQL(context.ExistingConnection, mySQLOptionsAction);
return context.DbContextOptions.UseMySql(context.ExistingConnection, mySQLOptionsAction);
}
else
{
return context.DbContextOptions.UseMySQL(context.ConnectionString, mySQLOptionsAction);
return context.DbContextOptions.UseMySql(context.ConnectionString, mySQLOptionsAction);
}
}
}

@ -1,6 +1,6 @@
using JetBrains.Annotations;
using MySql.Data.EntityFrameworkCore.Infraestructure;
using System;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Volo.Abp.EntityFrameworkCore
{
@ -8,7 +8,7 @@ namespace Volo.Abp.EntityFrameworkCore
{
public static void UseMySQL(
[NotNull] this AbpDbContextOptions options,
[CanBeNull] Action<MySQLDbContextOptionsBuilder> mySQLOptionsAction = null)
[CanBeNull] Action<MySqlDbContextOptionsBuilder> mySQLOptionsAction = null)
{
options.Configure(context =>
{
@ -18,7 +18,7 @@ namespace Volo.Abp.EntityFrameworkCore
public static void UseMySQL<TDbContext>(
[NotNull] this AbpDbContextOptions options,
[CanBeNull] Action<MySQLDbContextOptionsBuilder> mySQLOptionsAction = null)
[CanBeNull] Action<MySqlDbContextOptionsBuilder> mySQLOptionsAction = null)
where TDbContext : AbpDbContext<TDbContext>
{
options.Configure<TDbContext>(context =>

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

@ -14,7 +14,6 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
<ProjectReference Include="..\Volo.Abp.Data\Volo.Abp.Data.csproj" />
<ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" />
</ItemGroup>

@ -1,10 +1,12 @@
using Volo.Abp.Data;
using Volo.Abp.Modularity;
using Volo.Abp.Security;
namespace Volo.Abp.MultiTenancy
{
[DependsOn(
typeof(AbpDataModule)
typeof(AbpDataModule),
typeof(AbpSecurityModule)
)]
public class AbpMultiTenancyAbstractionsModule : AbpModule //TODO: Rename to AbpMultiTenancyModule?
{

@ -1,8 +1,10 @@
using Volo.Abp.Localization;
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security;
using Volo.Abp.Users;
namespace Volo.Abp.Settings
{
@ -13,6 +15,11 @@ namespace Volo.Abp.Settings
)]
public class AbpSettingsModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
AutoAddDefinitionProviders(context.Services);
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<SettingOptions>(options =>
@ -23,5 +30,23 @@ namespace Volo.Abp.Settings
options.ValueProviders.Add<UserSettingValueProvider>();
});
}
private static void AutoAddDefinitionProviders(IServiceCollection services)
{
var definitionProviders = new List<Type>();
services.OnRegistred(context =>
{
if (typeof(ISettingDefinitionProvider).IsAssignableFrom(context.ImplementationType))
{
definitionProviders.Add(context.ImplementationType);
}
});
services.Configure<SettingOptions>(options =>
{
options.DefinitionProviders.AddIfNotContains(definitionProviders);
});
}
}
}

@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Settings
{
[Dependency(TryRegister = true)]
public class NullSettingStore : ISettingStore, ISingletonDependency
{
public ILogger<NullSettingStore> Logger { get; set; }
@ -19,21 +19,5 @@ namespace Volo.Abp.Settings
{
return Task.FromResult((string) null);
}
public Task SetAsync(string name, string value, string providerName, string providerKey)
{
Logger.LogWarning($"Setting the value for {name} is not possible because current setting store is {nameof(NullSettingStore)}");
return Task.CompletedTask;
}
public Task<List<SettingValue>> GetListAsync(string providerName, string providerKey)
{
return Task.FromResult(new List<SettingValue>());
}
public Task DeleteAsync(string name, string providerName, string providerKey)
{
return Task.CompletedTask;
}
}
}

@ -40,7 +40,7 @@ namespace Volo.Abp.Settings
/// A list of allowed providers to get/set value of this setting.
/// An empty list indicates that all providers are allowed.
/// </summary>
public List<string> Providers { get; }
public List<string> Providers { get; } //TODO: Rename to AllowedProviders
/// <summary>
/// Is this setting inherited from parent scopes.

@ -10,22 +10,19 @@ namespace Volo.Abp.Settings
{
public class SettingDefinitionManager : ISettingDefinitionManager, ISingletonDependency
{
protected Lazy<List<ISettingDefinitionProvider>> Providers { get; }
protected Lazy<IDictionary<string, SettingDefinition>> SettingDefinitions { get; }
protected SettingOptions Options { get; }
private readonly IServiceProvider _serviceProvider;
protected IServiceProvider ServiceProvider { get; }
public SettingDefinitionManager(
IOptions<SettingOptions> options,
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
ServiceProvider = serviceProvider;
Options = options.Value;
Providers = new Lazy<List<ISettingDefinitionProvider>>(CreateSettingProviders, true);
SettingDefinitions = new Lazy<IDictionary<string, SettingDefinition>>(CreateSettingDefinitions, true);
}
@ -53,21 +50,21 @@ namespace Volo.Abp.Settings
return SettingDefinitions.Value.GetOrDefault(name);
}
protected virtual List<ISettingDefinitionProvider> CreateSettingProviders()
{
return Options
.DefinitionProviders
.Select(p => _serviceProvider.GetRequiredService(p) as ISettingDefinitionProvider)
.ToList();
}
protected virtual IDictionary<string, SettingDefinition> CreateSettingDefinitions()
{
var settings = new Dictionary<string, SettingDefinition>();
foreach (var provider in Providers.Value)
using (var scope = ServiceProvider.CreateScope())
{
provider.Define(new SettingDefinitionContext(settings));
var providers = Options
.DefinitionProviders
.Select(p => scope.ServiceProvider.GetRequiredService(p) as ISettingDefinitionProvider)
.ToList();
foreach (var provider in providers)
{
provider.Define(new SettingDefinitionContext(settings));
}
}
return settings;

@ -2,7 +2,7 @@
namespace Volo.Abp.Settings
{
public abstract class SettingDefinitionProvider : ISettingDefinitionProvider, ISingletonDependency
public abstract class SettingDefinitionProvider : ISettingDefinitionProvider, ITransientDependency
{
public abstract void Define(ISettingDefinitionContext context);
}

@ -29,7 +29,7 @@ namespace Volo.Abp.Settings
Providers = new Lazy<List<ISettingValueProvider>>(
() => Options
.ValueProviders
.Select(c => serviceProvider.GetRequiredService(c) as ISettingValueProvider)
.Select(type => serviceProvider.GetRequiredService(type) as ISettingValueProvider)
.ToList(),
true
);

@ -6,7 +6,6 @@ using Volo.Abp.AspNetCore.Mvc.Authorization;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.Localization.Resource;
using Volo.Abp.AspNetCore.TestBase;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Autofac;
using Volo.Abp.Localization;
using Volo.Abp.Localization.Resources.AbpValidation;

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

@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Authorization.TestServices;
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
@ -20,13 +19,5 @@ namespace Volo.Abp.Authorization
}
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<PermissionOptions>(options =>
{
options.DefinitionProviders.TryAdd<AuthorizationTestPermissionDefinitionProvider>();
});
}
}
}

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

@ -13,6 +13,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Settings\Volo.Abp.Settings.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />

@ -1,8 +1,10 @@
using Volo.Abp.Modularity;
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
namespace Volo.Abp.Settings
{
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpSettingsModule),
typeof(AbpTestBaseModule)
)]

@ -13,6 +13,11 @@ namespace Volo.Abp.Settings
_settingProvider = GetRequiredService<ISettingProvider>();
}
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
[Fact]
public async Task Should_Get_Null_If_No_Value_Provided_And_No_Default_Value()
{

@ -32,8 +32,10 @@ namespace Volo.Blogging.Users
}
}
user.Update(eventData.Entity);
await UserRepository.UpdateAsync(user);
if (user.Update(eventData.Entity))
{
await UserRepository.UpdateAsync(user);
}
}
}
}

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

@ -20,7 +20,5 @@ namespace Volo.Abp.TenantManagement
string filter = null,
bool includeDetails = false,
CancellationToken cancellationToken = default);
Task<long> GetCountAsync(CancellationToken cancellationToken = default);
}
}

@ -48,11 +48,6 @@ namespace Volo.Abp.TenantManagement.EntityFrameworkCore
.ToListAsync(cancellationToken);
}
public virtual async Task<long> GetCountAsync(CancellationToken cancellationToken = default)
{
return await DbSet.LongCountAsync(cancellationToken);
}
public override IQueryable<Tenant> WithDetails()
{
return GetQueryable().IncludeDetails();

@ -28,11 +28,6 @@ namespace Volo.Abp.TenantManagement.MongoDb
.FirstOrDefaultAsync(t => t.Name == name, cancellationToken);
}
public virtual async Task<long> GetCountAsync(CancellationToken cancellationToken = default)
{
return await GetMongoQueryable().LongCountAsync(cancellationToken);
}
public virtual async Task<List<Tenant>> GetListAsync(
string sorting = null,
int maxResultCount = int.MaxValue,

@ -3,7 +3,6 @@ using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.TenantManagement
@ -48,12 +47,6 @@ namespace Volo.Abp.TenantManagement
tenant.ConnectionStrings.Count.ShouldBeGreaterThanOrEqualTo(2);
}
[Fact]
public async Task GetCountAsync()
{
(await TenantRepository.GetCountAsync()).ShouldBeGreaterThan(0);
}
[Fact]
public async Task GetListAsync()
{

@ -1,5 +1,5 @@
{
"version": "0.5.1",
"version": "0.5.2",
"packages": [
"packs/*"
],

@ -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 have changed in this diff Show More

Loading…
Cancel
Save