98 changed files with 23229 additions and 380 deletions
@ -0,0 +1,20 @@ |
|||
using System.Threading; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public class AsyncLocalCurrentDataAccessAccessor : ICurrentDataAccessAccessor |
|||
{ |
|||
public static AsyncLocalCurrentDataAccessAccessor Instance { get; } = new(); |
|||
|
|||
public DataAccessOperation[] Current |
|||
{ |
|||
get => _currentScope.Value; |
|||
set => _currentScope.Value = value; |
|||
} |
|||
|
|||
private readonly AsyncLocal<DataAccessOperation[]> _currentScope; |
|||
private AsyncLocalCurrentDataAccessAccessor() |
|||
{ |
|||
_currentScope = new AsyncLocal<DataAccessOperation[]>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using System; |
|||
using Volo.Abp.EventBus; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
[Serializable] |
|||
[EventName("abp.data_protection.entity_auth_created")] |
|||
public class DataAccessEntityAuthCreateEvent : IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
public string[] EntityKeys { get; set; } |
|||
public string EntityKeyType { get; set; } |
|||
public string EntityType { get; set; } |
|||
public string[] Roles { get; set; } |
|||
public string[] OrganizationUnits { get; set; } |
|||
public DataAccessEntityAuthCreateEvent() |
|||
{ |
|||
|
|||
} |
|||
public DataAccessEntityAuthCreateEvent( |
|||
string entityType, |
|||
string entityKeyType, |
|||
string[] entityKeys, |
|||
string[] roles = null, |
|||
string[] organizationUnits = null, |
|||
Guid? tenantId = null) |
|||
{ |
|||
EntityType = entityType; |
|||
EntityKeyType = entityKeyType; |
|||
EntityKeys = entityKeys; |
|||
EntityType = entityType; |
|||
Roles = roles; |
|||
OrganizationUnits = organizationUnits; |
|||
TenantId = tenantId; |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System; |
|||
using Volo.Abp; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public class DataAccessScope : IDataAccessScope, ITransientDependency |
|||
{ |
|||
public DataAccessOperation[] Operations => _currentDataAccessAccessor.Current; |
|||
|
|||
private readonly ICurrentDataAccessAccessor _currentDataAccessAccessor; |
|||
public DataAccessScope(ICurrentDataAccessAccessor currentDataAccessAccessor) |
|||
{ |
|||
_currentDataAccessAccessor = currentDataAccessAccessor; |
|||
} |
|||
|
|||
public IDisposable BeginScope(DataAccessOperation[] operations = null) |
|||
{ |
|||
var parentScope = _currentDataAccessAccessor.Current; |
|||
_currentDataAccessAccessor.Current = operations; |
|||
|
|||
return new DisposeAction<ValueTuple<ICurrentDataAccessAccessor, DataAccessOperation[]>>(static (state) => |
|||
{ |
|||
var (currentDataAccessAccessor, parentScope) = state; |
|||
currentDataAccessAccessor.Current = parentScope; |
|||
}, (_currentDataAccessAccessor, parentScope)); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
namespace LINGYUN.Abp.DataProtection; |
|||
/// <summary>
|
|||
/// 数据访问策略
|
|||
/// </summary>
|
|||
public enum DataAccessStrategy |
|||
{ |
|||
/// <summary>
|
|||
/// 可以访问所有数据
|
|||
/// </summary>
|
|||
All, |
|||
/// <summary>
|
|||
/// 自定义规则
|
|||
/// </summary>
|
|||
Custom, |
|||
/// <summary>
|
|||
/// 仅当前用户
|
|||
/// </summary>
|
|||
CurrentUser, |
|||
/// <summary>
|
|||
/// 仅当前用户角色
|
|||
/// </summary>
|
|||
CurrentRoles, |
|||
/// <summary>
|
|||
/// 仅当前用户组织机构
|
|||
/// </summary>
|
|||
CurrentOrganizationUnits, |
|||
/// <summary>
|
|||
/// 仅当前用户组织机构及下级机构
|
|||
/// </summary>
|
|||
CurrentAndSubOrganizationUnits, |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
[Serializable] |
|||
public class DataAccessStrategyState |
|||
{ |
|||
/// <summary>
|
|||
/// 权限主体
|
|||
/// </summary>
|
|||
public string SubjectName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 权限主体标识
|
|||
/// </summary>
|
|||
public string[] SubjectKeys { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 权限策略
|
|||
/// </summary>
|
|||
public DataAccessStrategy Strategy { get; set; } |
|||
public DataAccessStrategyState() |
|||
{ |
|||
|
|||
} |
|||
public DataAccessStrategyState( |
|||
string subjectName, |
|||
string[] subjectKeys, |
|||
DataAccessStrategy strategy) |
|||
{ |
|||
SubjectName = subjectName; |
|||
SubjectKeys = subjectKeys; |
|||
Strategy = strategy; |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public interface ICurrentDataAccessAccessor |
|||
{ |
|||
DataAccessOperation[] Current { get; set; } |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public interface IDataAccessScope |
|||
{ |
|||
DataAccessOperation[] Operations { get; } |
|||
IDisposable BeginScope(DataAccessOperation[] operations = null); |
|||
} |
|||
@ -1,8 +1,8 @@ |
|||
using Volo.Abp.Data; |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public interface IDataProtected : IHasExtraProperties |
|||
public interface IDataProtected |
|||
{ |
|||
|
|||
Guid? CreatorId { get; } |
|||
} |
|||
@ -1,3 +0,0 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -1,24 +0,0 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks> |
|||
<AssemblyName>LINGYUN.Abp.DataProtection.Application.Contracts</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.DataProtection.Application.Contracts</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.DataProtection.Abstractions\LINGYUN.Abp.DataProtection.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -1,12 +0,0 @@ |
|||
using Volo.Abp.Application; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpDataProtectionAbstractionsModule), |
|||
typeof(AbpDddApplicationContractsModule))] |
|||
public class AbpDataProtectionApplicationContractsModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
@ -1,7 +0,0 @@ |
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public class EntityEnumInfoDto |
|||
{ |
|||
public string Key { get; set; } |
|||
public object Value { get; set; } |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public class EntityPropertyInfoDto |
|||
{ |
|||
/// <summary>
|
|||
/// 名称
|
|||
/// </summary>
|
|||
public string Name { get; set; } |
|||
/// <summary>
|
|||
/// 显示名称
|
|||
/// </summary>
|
|||
public string DisplayName { get; set; } |
|||
/// <summary>
|
|||
/// 类型全名
|
|||
/// </summary>
|
|||
public string TypeFullName { get; set; } |
|||
/// <summary>
|
|||
/// JavaScript类型
|
|||
/// </summary>
|
|||
public string JavaScriptType { get; set; } |
|||
/// <summary>
|
|||
/// 枚举列表
|
|||
/// </summary>
|
|||
public EntityEnumInfoDto[] Enums { get; set; } = new EntityEnumInfoDto[0]; |
|||
/// <summary>
|
|||
/// 允许的过滤操作列表
|
|||
/// </summary>
|
|||
public DataAccessFilterOperate[] Operates { get; set; } |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public class EntityTypeInfoDto |
|||
{ |
|||
/// <summary>
|
|||
/// 实体名称
|
|||
/// </summary>
|
|||
public string Name { get; set; } |
|||
/// <summary>
|
|||
/// 显示名称
|
|||
/// </summary>
|
|||
public string DisplayName { get; set; } |
|||
/// <summary>
|
|||
/// 可访问属性列表
|
|||
/// </summary>
|
|||
public EntityPropertyInfoDto[] Properties { get; set; } = new EntityPropertyInfoDto[0]; |
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public class EntityTypeInfoGetInput |
|||
{ |
|||
public DataAccessOperation Operation { get; set; } |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public interface IEntityTypeInfoAppService : IApplicationService |
|||
{ |
|||
/// <summary>
|
|||
/// 获取实体可访问规则
|
|||
/// </summary>
|
|||
/// <param name="input"></param>
|
|||
/// <returns></returns>
|
|||
Task<EntityTypeInfoDto> GetEntityRuleAsync(EntityTypeInfoGetInput input); |
|||
} |
|||
@ -1,3 +0,0 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -1,25 +0,0 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks> |
|||
<AssemblyName>LINGYUN.Abp.DataProtection.Application</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.DataProtection.Application</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.DataProtection\LINGYUN.Abp.DataProtection.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.DataProtection.Application.Contracts\LINGYUN.Abp.DataProtection.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -1,13 +0,0 @@ |
|||
using Volo.Abp.Application; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpDataProtectionApplicationContractsModule), |
|||
typeof(AbpDataProtectionModule), |
|||
typeof(AbpDddApplicationModule))] |
|||
public class AbpDataProtectionApplicationModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
@ -1,47 +0,0 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Localization; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public abstract class EntityTypeInfoAppService<TEntity> : ApplicationService, IEntityTypeInfoAppService |
|||
{ |
|||
protected IDataAccessEntityTypeInfoProvider EntityTypeInfoProvider => LazyServiceProvider.GetRequiredService<IDataAccessEntityTypeInfoProvider>(); |
|||
|
|||
[Authorize] |
|||
public virtual async Task<EntityTypeInfoDto> GetEntityRuleAsync(EntityTypeInfoGetInput input) |
|||
{ |
|||
var entityType = typeof(TEntity); |
|||
var resourceType = LocalizationResource ?? typeof(DefaultResource); |
|||
|
|||
var context = new DataAccessEntitTypeInfoContext( |
|||
entityType, |
|||
resourceType, |
|||
input.Operation, |
|||
LazyServiceProvider); |
|||
|
|||
var model = await EntityTypeInfoProvider.GetEntitTypeInfoAsync(context); |
|||
|
|||
return new EntityTypeInfoDto |
|||
{ |
|||
Name = model.Name, |
|||
DisplayName = model.DisplayName, |
|||
Properties = model.Properties.Select(prop => new EntityPropertyInfoDto |
|||
{ |
|||
Name = prop.Name, |
|||
DisplayName = prop.DisplayName, |
|||
TypeFullName = prop.TypeFullName, |
|||
JavaScriptType = prop.JavaScriptType, |
|||
Operates = prop.Operates, |
|||
Enums = prop.Enums.Select(em => new EntityEnumInfoDto |
|||
{ |
|||
Key = em.Key, |
|||
Value = em.Value, |
|||
}).ToArray() |
|||
}).ToArray(), |
|||
}; |
|||
} |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
namespace System; |
|||
|
|||
internal static class NullableTypeExtensions |
|||
{ |
|||
public static bool IsNullableType(this Type theType) => |
|||
theType.IsGenericType(typeof(Nullable<>)); |
|||
|
|||
public static bool IsGenericType(this Type type, Type genericType) => |
|||
type.IsGenericType && type.GetGenericTypeDefinition() == genericType; |
|||
} |
|||
@ -0,0 +1,172 @@ |
|||
using Microsoft.EntityFrameworkCore; |
|||
using System; |
|||
using System.Linq; |
|||
using System.Linq.Expressions; |
|||
using System.Reflection; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection.EntityFrameworkCore; |
|||
|
|||
public class EfCoreDataAccessStrategyFilterBuilder : DataAccessStrategyFilterBuilderBase |
|||
{ |
|||
private readonly ICurrentUser _currentUser; |
|||
|
|||
private static readonly MethodInfo LikeMethodInfo |
|||
= typeof(DbFunctionsExtensions) |
|||
.GetMethod( |
|||
nameof(DbFunctionsExtensions.Like), |
|||
new[] { typeof(DbFunctions), typeof(string), typeof(string) }); |
|||
|
|||
private static readonly MethodInfo ContainsMethodInfo |
|||
= typeof(Enumerable) |
|||
.GetMethods() |
|||
.First(m => m.Name == nameof(Enumerable.Contains) && m.GetParameters().Length == 2) |
|||
.MakeGenericMethod(typeof(string)); |
|||
|
|||
public EfCoreDataAccessStrategyFilterBuilder( |
|||
ICurrentUser currentUser, |
|||
IDataFilter dataFilter, |
|||
IDataAccessScope dataAccessScope, |
|||
IDataAccessStrategyStateProvider strategyStateProvider) |
|||
: base(dataFilter, dataAccessScope, strategyStateProvider) |
|||
{ |
|||
_currentUser = currentUser; |
|||
} |
|||
|
|||
protected override IQueryable<TEntity> Build<TEntity, TKey, TEntityAuth>(IQueryable<TEntity> entity, IQueryable<TEntityAuth> entityAuth, DataAccessStrategyState state) |
|||
{ |
|||
var parameterEntity = Expression.Parameter(typeof(TEntity), "e"); |
|||
var parameterEntityAuth = Expression.Parameter(typeof(TEntityAuth), "ea"); |
|||
|
|||
var entityIdProperty = Expression.Property(parameterEntity, nameof(IEntity<TKey>.Id)); |
|||
var entityIdAuthProperty = Expression.Property(parameterEntityAuth, nameof(DataAuthBase<TEntity, TKey>.EntityId)); |
|||
|
|||
// e.Id = ea.EntityId
|
|||
var entityIdMatch = Expression.Equal(entityIdAuthProperty, entityIdProperty); |
|||
|
|||
switch (state.Strategy) |
|||
{ |
|||
case DataAccessStrategy.CurrentUser: |
|||
var creatorIdProperty = Expression.Property(parameterEntity, nameof(IDataProtected.CreatorId)); |
|||
var creatorId = Expression.Constant(_currentUser.Id, typeof(Guid?)); |
|||
var currentUserExp = Expression.Equal(creatorIdProperty, creatorId); |
|||
// e => e.CreatorId = _currentUser.Id
|
|||
var currentUserFunc = Expression.Lambda<Func<TEntity, bool>>(currentUserExp, parameterEntity); |
|||
return entity.Where(currentUserFunc); |
|||
|
|||
case DataAccessStrategy.CurrentRoles: |
|||
var roleProperty = Expression.Property( |
|||
parameterEntityAuth, |
|||
nameof(DataAuthBase<TEntity, TKey>.Role) |
|||
); |
|||
|
|||
var roleContains = Expression.Call( |
|||
ContainsMethodInfo, |
|||
Expression.Constant(_currentUser.Roles), |
|||
roleProperty |
|||
); |
|||
|
|||
// and ea.Role in ("Users") and e.Id = ea.EntityId
|
|||
var roleFinalCondition = Expression.AndAlso(roleContains, entityIdMatch); |
|||
|
|||
var existsSubQueryWithRole = Expression.Call( |
|||
typeof(Queryable), |
|||
"Any", |
|||
new Type[] { typeof(TEntityAuth) }, |
|||
entityAuth.Expression, |
|||
Expression.Lambda<Func<TEntityAuth, bool>>(roleFinalCondition, parameterEntityAuth) |
|||
); |
|||
|
|||
var whereExistsCondition = Expression.Call( |
|||
typeof(Queryable), |
|||
"Where", |
|||
new Type[] { typeof(TEntity) }, |
|||
entity.Expression, |
|||
Expression.Lambda<Func<TEntity, bool>>( |
|||
existsSubQueryWithRole, parameterEntity |
|||
) |
|||
); |
|||
return entity.Provider.CreateQuery<TEntity>(whereExistsCondition); |
|||
case DataAccessStrategy.CurrentOrganizationUnits: |
|||
var equalOrganizationUnits = _currentUser.FindOrganizationUnits(); |
|||
|
|||
var ouProperty = Expression.Property( |
|||
parameterEntityAuth, |
|||
nameof(DataAuthBase<TEntity, TKey>.OrganizationUnit) |
|||
); |
|||
|
|||
var ouContains = Expression.Call( |
|||
ContainsMethodInfo, |
|||
Expression.Constant(equalOrganizationUnits), |
|||
ouProperty |
|||
); |
|||
// and ea.OrganizationUnit in ("00001.00001") and e.Id = ea.EntityId
|
|||
var finalCondition = Expression.AndAlso(ouContains, entityIdMatch); |
|||
|
|||
var existsSubQueryWithOu = Expression.Call( |
|||
typeof(Queryable), |
|||
"Any", |
|||
new Type[] { typeof(TEntityAuth) }, |
|||
entityAuth.Expression, |
|||
Expression.Lambda<Func<TEntityAuth, bool>>(finalCondition, parameterEntityAuth) |
|||
); |
|||
|
|||
var whereExistsConditionWithOu = Expression.Call( |
|||
typeof(Queryable), |
|||
"Where", |
|||
new Type[] { typeof(TEntity) }, |
|||
entity.Expression, |
|||
Expression.Lambda<Func<TEntity, bool>>( |
|||
existsSubQueryWithOu, parameterEntity |
|||
) |
|||
); |
|||
|
|||
return entity.Provider.CreateQuery<TEntity>(whereExistsConditionWithOu); |
|||
|
|||
case DataAccessStrategy.CurrentAndSubOrganizationUnits: |
|||
var startsWithOrganizationUnits = _currentUser.FindOrganizationUnits(); |
|||
|
|||
var dbFunctions = Expression.Property(null, typeof(EF), nameof(EF.Functions)); |
|||
|
|||
var filteredEntityAuth = startsWithOrganizationUnits |
|||
.Select(ouCode => |
|||
(Expression)Expression.Call( |
|||
typeof(Queryable), |
|||
"Any", |
|||
new Type[] { typeof(TEntityAuth) }, |
|||
entityAuth.Expression, |
|||
Expression.Lambda<Func<TEntityAuth, bool>>( |
|||
Expression.AndAlso( |
|||
Expression.Equal( |
|||
Expression.Property(parameterEntityAuth, nameof(DataAuthBase<TEntity, TKey>.EntityId)), |
|||
Expression.Property(parameterEntity, nameof(IEntity<TKey>.Id)) |
|||
), |
|||
Expression.Call( |
|||
LikeMethodInfo, |
|||
dbFunctions, |
|||
Expression.Property(parameterEntityAuth, nameof(DataAuthBase<TEntity, TKey>.OrganizationUnit)), |
|||
Expression.Constant(ouCode + "%") |
|||
) |
|||
), |
|||
parameterEntityAuth |
|||
) |
|||
) |
|||
) |
|||
.Aggregate(Expression.OrElse); |
|||
|
|||
var startsWithWhereExistsConditionWithOu = Expression.Call( |
|||
typeof(Queryable), |
|||
"Where", |
|||
new Type[] { typeof(TEntity) }, |
|||
entity.Expression, |
|||
Expression.Lambda<Func<TEntity, bool>>(filteredEntityAuth, parameterEntity) |
|||
); |
|||
|
|||
return entity.Provider.CreateQuery<TEntity>(startsWithWhereExistsConditionWithOu); |
|||
} |
|||
|
|||
return entity; |
|||
} |
|||
} |
|||
@ -1,15 +1,16 @@ |
|||
using System; |
|||
using System.Linq.Expressions; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
public class DataAccessKeywordContributorContext |
|||
{ |
|||
public IServiceProvider ServiceProvider { get; } |
|||
public Type ConversionType { get; } |
|||
public LambdaExpression Expression { get; } |
|||
public DataAccessKeywordContributorContext( |
|||
IServiceProvider serviceProvider, |
|||
Type conversionType) |
|||
LambdaExpression expression) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
ConversionType = conversionType; |
|||
Expression = expression; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public class DataAccessStrategyContributorContext |
|||
{ |
|||
public IServiceProvider ServiceProvider { get; } |
|||
public DataAccessStrategyContributorContext(IServiceProvider serviceProvider) |
|||
{ |
|||
ServiceProvider = serviceProvider; |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System.Linq; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public class DataAccessStrategyFilterBuildResult<TEntity> |
|||
{ |
|||
public DataAccessStrategy Strategy { get; } |
|||
public IQueryable<TEntity> Queryable { get; } |
|||
public DataAccessStrategyFilterBuildResult( |
|||
DataAccessStrategy strategy, |
|||
IQueryable<TEntity> queryable) |
|||
{ |
|||
Strategy = strategy; |
|||
Queryable = queryable; |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Data; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public abstract class DataAccessStrategyFilterBuilderBase : IDataAccessStrategyFilterBuilder |
|||
{ |
|||
private readonly IDataFilter _dataFilter; |
|||
private readonly IDataAccessScope _dataAccessScope; |
|||
private readonly IDataAccessStrategyStateProvider _strategyStateProvider; |
|||
protected DataAccessStrategyFilterBuilderBase( |
|||
IDataFilter dataFilter, |
|||
IDataAccessScope dataAccessScope, |
|||
IDataAccessStrategyStateProvider strategyStateProvider) |
|||
{ |
|||
_dataFilter = dataFilter; |
|||
_dataAccessScope = dataAccessScope; |
|||
_strategyStateProvider = strategyStateProvider; |
|||
} |
|||
|
|||
public async virtual Task<DataAccessStrategyFilterBuildResult<TEntity>> Build<TEntity, TKey, TEntityAuth>(IQueryable<TEntity> entity, IQueryable<TEntityAuth> entityAuth) |
|||
where TEntityAuth : DataAuthBase<TEntity, TKey> |
|||
{ |
|||
if (ShouldApplyFilter(typeof(TEntity), DataAccessOperation.Read)) |
|||
{ |
|||
|
|||
var strategyState = await _strategyStateProvider.GetOrNullAsync(); |
|||
if (strategyState != null && strategyState.Strategy != DataAccessStrategy.Custom) |
|||
{ |
|||
var newQueryable = Build<TEntity, TKey, TEntityAuth>(entity, entityAuth, strategyState); |
|||
|
|||
return new DataAccessStrategyFilterBuildResult<TEntity>( |
|||
strategyState.Strategy, |
|||
newQueryable); |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
protected virtual bool ShouldApplyFilter(Type entityType, DataAccessOperation operation) |
|||
{ |
|||
// TODO: 使用一个范围标志来确定当前需要禁用的数据权限操作
|
|||
if (!_dataFilter.IsEnabled<IDataProtected>()) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (entityType.IsDefined(typeof(DisableDataProtectedAttribute), true)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (_dataAccessScope.Operations != null && !_dataAccessScope.Operations.Contains(operation)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
protected abstract IQueryable<TEntity> Build<TEntity, TKey, TEntityAuth>(IQueryable<TEntity> entity, IQueryable<TEntityAuth> entityAuth, DataAccessStrategyState state) |
|||
where TEntityAuth : DataAuthBase<TEntity, TKey>; |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public class DataAccessStrategyStateProvider : IDataAccessStrategyStateProvider, ITransientDependency |
|||
{ |
|||
private readonly AbpDataProtectionOptions _options; |
|||
private readonly IServiceScopeFactory _serviceScopeFactory; |
|||
public DataAccessStrategyStateProvider( |
|||
IOptions<AbpDataProtectionOptions> options, |
|||
IServiceScopeFactory serviceScopeFactory) |
|||
{ |
|||
_options = options.Value; |
|||
_serviceScopeFactory = serviceScopeFactory; |
|||
} |
|||
|
|||
public async virtual Task<DataAccessStrategyState> GetOrNullAsync() |
|||
{ |
|||
using var scope = _serviceScopeFactory.CreateScope(); |
|||
var context = new DataAccessStrategyContributorContext(scope.ServiceProvider); |
|||
|
|||
foreach (var contributor in _options.StrategyContributors) |
|||
{ |
|||
var strategyState = await contributor.GetOrNullAsync(context); |
|||
if (strategyState != null) |
|||
{ |
|||
return strategyState; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public abstract class DataAuthBase<TEntity, TKey> : Entity<long>, IMultiTenant |
|||
{ |
|||
public virtual Guid? TenantId { get; protected set; } |
|||
public virtual TKey EntityId { get; protected set; } |
|||
public virtual TEntity Entity { get; protected set; } |
|||
public virtual string EntityType { get; protected set; } |
|||
public virtual string Role { get; protected set; } |
|||
public virtual string OrganizationUnit { get; protected set; } |
|||
protected DataAuthBase() |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected DataAuthBase( |
|||
TKey entityId, |
|||
string role, |
|||
string organizationUnit, |
|||
Guid? tenantId = null) |
|||
{ |
|||
TenantId = tenantId; |
|||
EntityId = entityId; |
|||
Role = role; |
|||
OrganizationUnit = organizationUnit; |
|||
|
|||
EntityType = typeof(TEntity).FullName; |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public interface IDataAccessStrategyContributor |
|||
{ |
|||
string Name { get; } |
|||
Task<DataAccessStrategyState> GetOrNullAsync(DataAccessStrategyContributorContext context); |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
/// <summary>
|
|||
/// 数据权限策略过滤器
|
|||
/// </summary>
|
|||
public interface IDataAccessStrategyFilterBuilder |
|||
{ |
|||
Task<DataAccessStrategyFilterBuildResult<TEntity>> Build<TEntity, TKey, TEntityAuth>(IQueryable<TEntity> entity, IQueryable<TEntityAuth> entityAuth) |
|||
where TEntityAuth : DataAuthBase<TEntity, TKey>; |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
public interface IDataAccessStrategyStateProvider |
|||
{ |
|||
Task<DataAccessStrategyState> GetOrNullAsync(); |
|||
} |
|||
@ -1,57 +0,0 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System.Collections.Concurrent; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
|
|||
[Dependency(ServiceLifetime.Singleton, TryRegister = true)] |
|||
public class InMemoryDataProtectedResourceStore : IDataProtectedResourceStore |
|||
{ |
|||
private readonly static ConcurrentDictionary<string, DataAccessResource> _cache = new ConcurrentDictionary<string, DataAccessResource>(); |
|||
public DataAccessResource Get(string subjectName, string subjectId, string entityTypeFullName, DataAccessOperation operation) |
|||
{ |
|||
var key = NormalizeKey(subjectName, subjectId, entityTypeFullName, operation); |
|||
if (_cache.TryGetValue(key, out var resource)) |
|||
{ |
|||
return resource; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
public void Remove(DataAccessResource resource) |
|||
{ |
|||
var key = NormalizeKey(resource.SubjectName, resource.SubjectId, resource.EntityTypeFullName, resource.Operation); |
|||
_cache.TryRemove(key, out var _); |
|||
} |
|||
|
|||
public void Set(DataAccessResource resource) |
|||
{ |
|||
var key = NormalizeKey(resource.SubjectName, resource.SubjectId, resource.EntityTypeFullName, resource.Operation); |
|||
_cache.TryAdd(key, resource); |
|||
} |
|||
|
|||
private static string NormalizeKey(string subjectName, string subjectId, string entityTypeFullName, DataAccessOperation operation) |
|||
{ |
|||
return $"{subjectName}_{subjectId}_{entityTypeFullName}_{operation}"; |
|||
} |
|||
|
|||
public Task SetAsync(DataAccessResource resource) |
|||
{ |
|||
Set(resource); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task RemoveAsync(DataAccessResource resource) |
|||
{ |
|||
Remove(resource); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public Task<DataAccessResource> GetAsync(string subjectName, string subjectId, string entityTypeFullName, DataAccessOperation operation) |
|||
{ |
|||
return Task.FromResult(Get(subjectName, subjectId, entityTypeFullName, operation)); |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
using System; |
|||
using Volo.Abp.Caching; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection.Stores; |
|||
|
|||
[Serializable] |
|||
[CacheName("AbpDataProtectionStrategyStates")] |
|||
public class DataAccessStrategyStateCacheItem |
|||
{ |
|||
private const string CacheKeyFormat = "sn:{0};si:{1}"; |
|||
/// <summary>
|
|||
/// 权限主体
|
|||
/// </summary>
|
|||
public string SubjectName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 权限主体标识
|
|||
/// </summary>
|
|||
public string SubjectId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 权限策略
|
|||
/// </summary>
|
|||
public DataAccessStrategy Strategy { get; set; } |
|||
public DataAccessStrategyStateCacheItem() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public DataAccessStrategyStateCacheItem( |
|||
string subjectName, |
|||
string subjectId, |
|||
DataAccessStrategy strategy) |
|||
{ |
|||
SubjectName = subjectName; |
|||
SubjectId = subjectId; |
|||
Strategy = strategy; |
|||
} |
|||
|
|||
public static string CalculateCacheKey(string subjectName, string subjectId) |
|||
{ |
|||
return string.Format(CacheKeyFormat, subjectName, subjectId); |
|||
} |
|||
} |
|||
@ -1,10 +1,9 @@ |
|||
using LINGYUN.Abp.DataProtection; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
namespace LINGYUN.Abp.DataProtection.Stores; |
|||
public class DataProtectedResourceCache : IDataProtectedResourceCache, ITransientDependency |
|||
{ |
|||
private readonly IDistributedCache<DataProtectedResourceCacheItem> _cache; |
|||
@ -1,10 +1,11 @@ |
|||
using LINGYUN.Abp.DataProtection; |
|||
using System; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Caching; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
namespace LINGYUN.Abp.DataProtection.Stores; |
|||
|
|||
[Serializable] |
|||
[CacheName("AbpDataProtectionResources")] |
|||
public class DataProtectedResourceCacheItem |
|||
{ |
|||
private const string CacheKeyFormat = "sn:{0};si:{1},e:{2},o:{3}"; |
|||
@ -1,17 +1,13 @@ |
|||
using LINGYUN.Abp.DataProtection; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System.Threading.Tasks; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
namespace LINGYUN.Abp.DataProtection.Stores; |
|||
|
|||
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)] |
|||
[ExposeServices(typeof(IDataProtectedResourceStore), typeof(DataProtectedResourceCacheStore))] |
|||
public class DataProtectedResourceCacheStore : IDataProtectedResourceStore, ITransientDependency |
|||
public class DataProtectedResourceStore : IDataProtectedResourceStore, ITransientDependency |
|||
{ |
|||
private readonly IDataProtectedResourceCache _cache; |
|||
|
|||
public DataProtectedResourceCacheStore(IDataProtectedResourceCache cache) |
|||
public DataProtectedResourceStore(IDataProtectedResourceCache cache) |
|||
{ |
|||
_cache = cache; |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection.Stores; |
|||
|
|||
public class DataProtectedStrategyStateCache : IDataProtectedStrategyStateCache, ITransientDependency |
|||
{ |
|||
private readonly IDistributedCache<DataAccessStrategyStateCacheItem> _cache; |
|||
|
|||
public DataProtectedStrategyStateCache(IDistributedCache<DataAccessStrategyStateCacheItem> cache) |
|||
{ |
|||
_cache = cache; |
|||
} |
|||
|
|||
public async virtual Task<DataAccessStrategyStateCacheItem> GetAsync(string subjectName, string subjectId) |
|||
{ |
|||
var cacheKey = DataAccessStrategyStateCacheItem.CalculateCacheKey(subjectName, subjectId); |
|||
var cacheItem = await _cache.GetAsync(cacheKey); |
|||
return cacheItem; |
|||
} |
|||
|
|||
public async virtual Task RemoveAsync(DataAccessStrategyState state) |
|||
{ |
|||
foreach (var subjectKey in state.SubjectKeys) |
|||
{ |
|||
var cacheKey = DataAccessStrategyStateCacheItem.CalculateCacheKey(state.SubjectName, subjectKey); |
|||
await _cache.RemoveAsync(cacheKey); |
|||
} |
|||
} |
|||
|
|||
public async virtual Task SetAsync(DataAccessStrategyState state) |
|||
{ |
|||
foreach (var subjectKey in state.SubjectKeys) |
|||
{ |
|||
var cacheKey = DataAccessStrategyStateCacheItem.CalculateCacheKey(state.SubjectName, subjectKey); |
|||
var cacheItem = new DataAccessStrategyStateCacheItem(state.SubjectName, subjectKey, state.Strategy); |
|||
await _cache.SetAsync(cacheKey, cacheItem, new DistributedCacheEntryOptions()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection.Stores; |
|||
|
|||
public class DataProtectedStrategyStateStore : IDataProtectedStrategyStateStore, ITransientDependency |
|||
{ |
|||
private readonly IDataProtectedStrategyStateCache _cache; |
|||
|
|||
public DataProtectedStrategyStateStore(IDataProtectedStrategyStateCache cache) |
|||
{ |
|||
_cache = cache; |
|||
} |
|||
|
|||
public async virtual Task<DataAccessStrategyState> GetOrNullAsync(string subjectName, string subjectId) |
|||
{ |
|||
var cacheItem = await _cache.GetAsync(subjectName, subjectId); |
|||
if (cacheItem == null ) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
return new DataAccessStrategyState( |
|||
cacheItem.SubjectName, |
|||
new string[] { cacheItem.SubjectId }, |
|||
cacheItem.Strategy); |
|||
} |
|||
|
|||
public async virtual Task RemoveAsync(DataAccessStrategyState state) |
|||
{ |
|||
await _cache.RemoveAsync(state); |
|||
} |
|||
|
|||
public async virtual Task SetAsync(DataAccessStrategyState state) |
|||
{ |
|||
await _cache.SetAsync(state); |
|||
} |
|||
} |
|||
@ -1,7 +1,6 @@ |
|||
using LINGYUN.Abp.DataProtection; |
|||
using System.Threading.Tasks; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
namespace LINGYUN.Abp.DataProtection.Stores; |
|||
public interface IDataProtectedResourceCache |
|||
{ |
|||
/// <summary>
|
|||
@ -1,6 +1,6 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection; |
|||
namespace LINGYUN.Abp.DataProtection.Stores; |
|||
public interface IDataProtectedResourceStore |
|||
{ |
|||
Task SetAsync(DataAccessResource resource); |
|||
@ -0,0 +1,12 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection.Stores; |
|||
|
|||
public interface IDataProtectedStrategyStateCache |
|||
{ |
|||
Task SetAsync(DataAccessStrategyState state); |
|||
|
|||
Task RemoveAsync(DataAccessStrategyState state); |
|||
|
|||
Task<DataAccessStrategyStateCacheItem> GetAsync(string subjectName, string subjectId); |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection.Stores; |
|||
|
|||
public interface IDataProtectedStrategyStateStore |
|||
{ |
|||
Task SetAsync(DataAccessStrategyState state); |
|||
|
|||
Task RemoveAsync(DataAccessStrategyState state); |
|||
|
|||
Task<DataAccessStrategyState> GetOrNullAsync(string subjectName, string subjectId); |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using LINGYUN.Abp.DataProtection.Stores; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Authorization.Permissions; |
|||
using Volo.Abp.Users; |
|||
|
|||
namespace LINGYUN.Abp.DataProtection.Subjects; |
|||
|
|||
/// <summary>
|
|||
/// 角色数据权限策略
|
|||
/// </summary>
|
|||
public class DataAccessStrategyRoleNameContributor : IDataAccessStrategyContributor |
|||
{ |
|||
public string Name => RolePermissionValueProvider.ProviderName; |
|||
|
|||
public async virtual Task<DataAccessStrategyState> GetOrNullAsync(DataAccessStrategyContributorContext context) |
|||
{ |
|||
var states = new List<DataAccessStrategyState>(); |
|||
var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>(); |
|||
if (!currentUser.IsAuthenticated) |
|||
{ |
|||
return null; |
|||
} |
|||
var store = context.ServiceProvider.GetRequiredService<IDataProtectedStrategyStateStore>(); |
|||
foreach (var userRole in currentUser.Roles) |
|||
{ |
|||
var strategyState = await store.GetOrNullAsync(Name, userRole); |
|||
if (strategyState != null) |
|||
{ |
|||
states.Add(strategyState); |
|||
} |
|||
} |
|||
|
|||
// 多个角色配置过策略时, 取权重最大的策略生效
|
|||
return states.OrderByDescending(x => x.Strategy).FirstOrDefault(); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,41 @@ |
|||
using System; |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
|
|||
#nullable disable |
|||
|
|||
namespace LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.Migrations |
|||
{ |
|||
/// <inheritdoc />
|
|||
public partial class UpgradeAbpFrameworkTo911 : Migration |
|||
{ |
|||
/// <inheritdoc />
|
|||
protected override void Up(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.AddColumn<DateTime>( |
|||
name: "CreationTime", |
|||
table: "AbpRoles", |
|||
type: "datetime(6)", |
|||
nullable: false, |
|||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); |
|||
|
|||
migrationBuilder.AddColumn<DateTime>( |
|||
name: "CreationTime", |
|||
table: "AbpClaimTypes", |
|||
type: "datetime(6)", |
|||
nullable: false, |
|||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Down(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.DropColumn( |
|||
name: "CreationTime", |
|||
table: "AbpRoles"); |
|||
|
|||
migrationBuilder.DropColumn( |
|||
name: "CreationTime", |
|||
table: "AbpClaimTypes"); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,38 @@ |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
|
|||
#nullable disable |
|||
|
|||
namespace LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.Migrations |
|||
{ |
|||
/// <inheritdoc />
|
|||
public partial class ChangeAuthDataType : Migration |
|||
{ |
|||
/// <inheritdoc />
|
|||
protected override void Up(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.AlterColumn<string>( |
|||
name: "ExtraProperties", |
|||
table: "Demo_Books", |
|||
type: "json", |
|||
nullable: false, |
|||
oldClrType: typeof(string), |
|||
oldType: "longtext") |
|||
.Annotation("MySql:CharSet", "utf8mb4") |
|||
.OldAnnotation("MySql:CharSet", "utf8mb4"); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Down(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.AlterColumn<string>( |
|||
name: "ExtraProperties", |
|||
table: "Demo_Books", |
|||
type: "longtext", |
|||
nullable: false, |
|||
oldClrType: typeof(string), |
|||
oldType: "json") |
|||
.Annotation("MySql:CharSet", "utf8mb4") |
|||
.OldAnnotation("MySql:CharSet", "utf8mb4"); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,49 @@ |
|||
using System; |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
|
|||
#nullable disable |
|||
|
|||
namespace LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.Migrations |
|||
{ |
|||
/// <inheritdoc />
|
|||
public partial class AddSubjectStrategy : Migration |
|||
{ |
|||
/// <inheritdoc />
|
|||
protected override void Up(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.CreateTable( |
|||
name: "AbpAuthSubjectStrategys", |
|||
columns: table => new |
|||
{ |
|||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"), |
|||
IsEnabled = table.Column<bool>(type: "tinyint(1)", nullable: false), |
|||
TenantId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), |
|||
SubjectName = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: false) |
|||
.Annotation("MySql:CharSet", "utf8mb4"), |
|||
SubjectId = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: true) |
|||
.Annotation("MySql:CharSet", "utf8mb4"), |
|||
Strategy = table.Column<int>(type: "int", nullable: false), |
|||
ExtraProperties = table.Column<string>(type: "longtext", nullable: false) |
|||
.Annotation("MySql:CharSet", "utf8mb4"), |
|||
ConcurrencyStamp = table.Column<string>(type: "varchar(40)", maxLength: 40, nullable: false) |
|||
.Annotation("MySql:CharSet", "utf8mb4"), |
|||
CreationTime = table.Column<DateTime>(type: "datetime(6)", nullable: false), |
|||
CreatorId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), |
|||
LastModificationTime = table.Column<DateTime>(type: "datetime(6)", nullable: true), |
|||
LastModifierId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci") |
|||
}, |
|||
constraints: table => |
|||
{ |
|||
table.PrimaryKey("PK_AbpAuthSubjectStrategys", x => x.Id); |
|||
}) |
|||
.Annotation("MySql:CharSet", "utf8mb4"); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Down(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.DropTable( |
|||
name: "AbpAuthSubjectStrategys"); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,85 @@ |
|||
using System; |
|||
using Microsoft.EntityFrameworkCore.Metadata; |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
|
|||
#nullable disable |
|||
|
|||
namespace LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.Migrations |
|||
{ |
|||
/// <inheritdoc />
|
|||
public partial class AddBookAuth : Migration |
|||
{ |
|||
/// <inheritdoc />
|
|||
protected override void Up(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.AlterColumn<string>( |
|||
name: "ExtraProperties", |
|||
table: "Demo_Books", |
|||
type: "longtext", |
|||
nullable: false, |
|||
oldClrType: typeof(string), |
|||
oldType: "json") |
|||
.Annotation("MySql:CharSet", "utf8mb4") |
|||
.OldAnnotation("MySql:CharSet", "utf8mb4"); |
|||
|
|||
migrationBuilder.CreateTable( |
|||
name: "Demo_BooksAuths", |
|||
columns: table => new |
|||
{ |
|||
Id = table.Column<long>(type: "bigint", nullable: false) |
|||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), |
|||
TenantId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"), |
|||
EntityId = table.Column<Guid>(type: "char(64)", maxLength: 64, nullable: false, collation: "ascii_general_ci"), |
|||
EntityType = table.Column<string>(type: "varchar(128)", maxLength: 128, nullable: false) |
|||
.Annotation("MySql:CharSet", "utf8mb4"), |
|||
Role = table.Column<string>(type: "varchar(32)", maxLength: 32, nullable: true) |
|||
.Annotation("MySql:CharSet", "utf8mb4"), |
|||
OrganizationUnit = table.Column<string>(type: "varchar(20)", maxLength: 20, nullable: true) |
|||
.Annotation("MySql:CharSet", "utf8mb4") |
|||
}, |
|||
constraints: table => |
|||
{ |
|||
table.PrimaryKey("PK_Demo_BooksAuths", x => x.Id); |
|||
table.ForeignKey( |
|||
name: "FK_Demo_BooksAuths_Demo_Books_EntityId", |
|||
column: x => x.EntityId, |
|||
principalTable: "Demo_Books", |
|||
principalColumn: "Id", |
|||
onDelete: ReferentialAction.Cascade); |
|||
}) |
|||
.Annotation("MySql:CharSet", "utf8mb4"); |
|||
|
|||
migrationBuilder.CreateIndex( |
|||
name: "IX_Demo_BooksAuths_EntityId", |
|||
table: "Demo_BooksAuths", |
|||
column: "EntityId"); |
|||
|
|||
migrationBuilder.CreateIndex( |
|||
name: "IX_Demo_BooksAuths_OrganizationUnit", |
|||
table: "Demo_BooksAuths", |
|||
column: "OrganizationUnit"); |
|||
|
|||
migrationBuilder.CreateIndex( |
|||
name: "IX_Demo_BooksAuths_Role", |
|||
table: "Demo_BooksAuths", |
|||
column: "Role"); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Down(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.DropTable( |
|||
name: "Demo_BooksAuths"); |
|||
|
|||
migrationBuilder.AlterColumn<string>( |
|||
name: "ExtraProperties", |
|||
table: "Demo_Books", |
|||
type: "json", |
|||
nullable: false, |
|||
oldClrType: typeof(string), |
|||
oldType: "longtext") |
|||
.Annotation("MySql:CharSet", "utf8mb4") |
|||
.OldAnnotation("MySql:CharSet", "utf8mb4"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using LINGYUN.Abp.DataProtection; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
|
|||
public class SubjectStrategyDto |
|||
{ |
|||
public bool IsEnabled { get; set; } |
|||
public string SubjectName { get; set; } |
|||
public string SubjectId { get; set; } |
|||
public DataAccessStrategy Strategy { get; set; } |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
|
|||
public class SubjectStrategyGetInput |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(SubjectStrategyConsts), nameof(SubjectStrategyConsts.MaxSubjectNameLength))] |
|||
public string SubjectName { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(SubjectStrategyConsts), nameof(SubjectStrategyConsts.MaxSubjectIdLength))] |
|||
public string SubjectId { get; set; } |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using LINGYUN.Abp.DataProtection; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
|
|||
public class SubjectStrategySetInput |
|||
{ |
|||
public bool IsEnabled { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(SubjectStrategyConsts), nameof(SubjectStrategyConsts.MaxSubjectNameLength))] |
|||
public string SubjectName { get; set; } |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(SubjectStrategyConsts), nameof(SubjectStrategyConsts.MaxSubjectNameLength))] |
|||
public string SubjectId { get; set; } |
|||
|
|||
public DataAccessStrategy Strategy { get; set; } |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
|
|||
public interface ISubjectStrategyAppService : IApplicationService |
|||
{ |
|||
Task<SubjectStrategyDto> GetAsync(SubjectStrategyGetInput input); |
|||
|
|||
Task<SubjectStrategyDto> SetAsync(SubjectStrategySetInput input); |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using LINGYUN.Abp.DataProtection.Stores; |
|||
using LINGYUN.Abp.DataProtectionManagement.Permissions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
|
|||
[Authorize(DataProtectionManagementPermissionNames.SubjectStrategy.Default)] |
|||
public class SubjectStrategyAppService : DataProtectionManagementApplicationServiceBase, ISubjectStrategyAppService |
|||
{ |
|||
private readonly ISubjectStrategyRepository _repository; |
|||
private readonly IDataProtectedStrategyStateStore _strategyStateStore; |
|||
|
|||
public SubjectStrategyAppService( |
|||
ISubjectStrategyRepository repository, |
|||
IDataProtectedStrategyStateStore strategyStateStore) |
|||
{ |
|||
_repository = repository; |
|||
_strategyStateStore = strategyStateStore; |
|||
} |
|||
|
|||
public async virtual Task<SubjectStrategyDto> GetAsync(SubjectStrategyGetInput input) |
|||
{ |
|||
var subjectStrategy = await _repository.FindBySubjectAsync(input.SubjectName, input.SubjectId); |
|||
|
|||
return ObjectMapper.Map<SubjectStrategy, SubjectStrategyDto>(subjectStrategy); |
|||
} |
|||
|
|||
[Authorize(DataProtectionManagementPermissionNames.SubjectStrategy.Change)] |
|||
public async virtual Task<SubjectStrategyDto> SetAsync(SubjectStrategySetInput input) |
|||
{ |
|||
var subjectStrategy = await _repository.FindBySubjectAsync(input.SubjectName, input.SubjectId); |
|||
if (subjectStrategy == null) |
|||
{ |
|||
subjectStrategy = new SubjectStrategy( |
|||
GuidGenerator.Create(), |
|||
input.SubjectName, |
|||
input.SubjectId, |
|||
input.Strategy, |
|||
CurrentTenant.Id) |
|||
{ |
|||
IsEnabled = input.IsEnabled |
|||
}; |
|||
|
|||
await _repository.InsertAsync(subjectStrategy); |
|||
} |
|||
else |
|||
{ |
|||
subjectStrategy.IsEnabled = input.IsEnabled; |
|||
subjectStrategy.Strategy = input.Strategy; |
|||
|
|||
await _repository.UpdateAsync(subjectStrategy); |
|||
} |
|||
|
|||
await _strategyStateStore.SetAsync( |
|||
new DataProtection.DataAccessStrategyState( |
|||
subjectStrategy.SubjectName, |
|||
new string[] { subjectStrategy .SubjectId}, |
|||
subjectStrategy.Strategy)); |
|||
|
|||
return ObjectMapper.Map<SubjectStrategy, SubjectStrategyDto>(subjectStrategy); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
|
|||
public static class SubjectStrategyConsts |
|||
{ |
|||
public static int MaxSubjectNameLength { get; set; } = 30; |
|||
public static int MaxSubjectIdLength { get; set; } = 64; |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using LINGYUN.Abp.DataProtection; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
|
|||
public class DataAccessStrategyStateSynchronizer : IDistributedEventHandler<DataAccessResourceChangeEvent>, ITransientDependency |
|||
{ |
|||
private readonly ISubjectStrategyRepository _strategyRepository; |
|||
|
|||
public DataAccessStrategyStateSynchronizer(ISubjectStrategyRepository strategyRepository) |
|||
{ |
|||
_strategyRepository = strategyRepository; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public async virtual Task HandleEventAsync(DataAccessResourceChangeEvent eventData) |
|||
{ |
|||
if (eventData.IsEnabled) |
|||
{ |
|||
var subjectStrategy = await _strategyRepository.FindBySubjectAsync( |
|||
eventData.Resource.SubjectName, |
|||
eventData.Resource.SubjectId); |
|||
if (subjectStrategy != null) |
|||
{ |
|||
subjectStrategy.Strategy = DataAccessStrategy.Custom; |
|||
|
|||
await _strategyRepository.UpdateAsync(subjectStrategy); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
|
|||
public interface ISubjectStrategyRepository : IBasicRepository<SubjectStrategy, Guid> |
|||
{ |
|||
Task<SubjectStrategy> FindBySubjectAsync( |
|||
string subjectName, |
|||
string subjectId, |
|||
CancellationToken cancellationToken = default); |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using LINGYUN.Abp.DataProtection; |
|||
using System; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
|
|||
public class SubjectStrategy : AuditedAggregateRoot<Guid>, IMultiTenant |
|||
{ |
|||
public virtual bool IsEnabled { get; set; } |
|||
public virtual Guid? TenantId { get; protected set; } |
|||
public virtual string SubjectName { get; protected set; } |
|||
public virtual string SubjectId { get; protected set; } |
|||
public virtual DataAccessStrategy Strategy { get; set; } |
|||
protected SubjectStrategy() |
|||
{ |
|||
ExtraProperties = new ExtraPropertyDictionary(); |
|||
this.SetDefaultsForExtraProperties(); |
|||
} |
|||
|
|||
public SubjectStrategy( |
|||
Guid id, |
|||
string subjectName, |
|||
string subjectId, |
|||
DataAccessStrategy strategy, |
|||
Guid? tenantId = null) |
|||
: base(id) |
|||
{ |
|||
IsEnabled = true; |
|||
|
|||
SubjectName = Check.NotNullOrWhiteSpace(subjectName, nameof(subjectName), SubjectStrategyConsts.MaxSubjectNameLength); |
|||
SubjectId = Check.NotNullOrWhiteSpace(subjectId, nameof(subjectId), SubjectStrategyConsts.MaxSubjectIdLength); |
|||
Strategy = strategy; |
|||
TenantId = tenantId; |
|||
|
|||
ExtraProperties = new ExtraPropertyDictionary(); |
|||
this.SetDefaultsForExtraProperties(); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using Microsoft.EntityFrameworkCore; |
|||
using System; |
|||
using System.Linq; |
|||
using System.Linq.Dynamic.Core; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement.EntityFrameworkCore; |
|||
|
|||
public class EfCoreSubjectStrategyRepository : EfCoreRepository<IAbpDataProtectionManagementDbContext, SubjectStrategy, Guid>, ISubjectStrategyRepository |
|||
{ |
|||
public EfCoreSubjectStrategyRepository( |
|||
IDbContextProvider<IAbpDataProtectionManagementDbContext> dbContextProvider) |
|||
: base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public async virtual Task<SubjectStrategy> FindBySubjectAsync( |
|||
string subjectName, |
|||
string subjectId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
return await (await GetDbSetAsync()) |
|||
.Where(x => x.SubjectName == subjectName && x.SubjectId == subjectId) |
|||
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using LINGYUN.Abp.DataProtectionManagement.Permissions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace LINGYUN.Abp.DataProtectionManagement; |
|||
|
|||
[Controller] |
|||
[Authorize(DataProtectionManagementPermissionNames.SubjectStrategy.Default)] |
|||
[RemoteService(Name = DataProtectionManagementRemoteServiceConsts.RemoteServiceName)] |
|||
[Area(DataProtectionManagementRemoteServiceConsts.ModuleName)] |
|||
[Route($"api/{DataProtectionManagementRemoteServiceConsts.ModuleName}/subject-strategys")] |
|||
public class SubjectStrategyController : AbpControllerBase, ISubjectStrategyAppService |
|||
{ |
|||
private readonly ISubjectStrategyAppService _service; |
|||
public SubjectStrategyController(ISubjectStrategyAppService service) |
|||
{ |
|||
_service = service; |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual Task<SubjectStrategyDto> GetAsync(SubjectStrategyGetInput input) |
|||
{ |
|||
return _service.GetAsync(input); |
|||
} |
|||
|
|||
[HttpPut] |
|||
[Authorize(DataProtectionManagementPermissionNames.SubjectStrategy.Change)] |
|||
public virtual Task<SubjectStrategyDto> SetAsync(SubjectStrategySetInput input) |
|||
{ |
|||
return _service.SetAsync(input); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue