diff --git a/aspnet-core/LINGYUN.MicroService.Common.sln b/aspnet-core/LINGYUN.MicroService.Common.sln index e9129df5c..5e6c21124 100644 --- a/aspnet-core/LINGYUN.MicroService.Common.sln +++ b/aspnet-core/LINGYUN.MicroService.Common.sln @@ -182,6 +182,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Localization.Dy EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Data.DbMigrator", "modules\common\LINGYUN.Abp.Data.DbMigrator\LINGYUN.Abp.Data.DbMigrator.csproj", "{3993A315-B250-4C5D-98C7-90FD06841B66}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "data-protection", "data-protection", "{A0910407-CE69-4DC8-9721-F4324C22EEA8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.DataProtection", "modules\data-protection\LINGYUN.Abp.DataProtection\LINGYUN.Abp.DataProtection.csproj", "{519BF5DA-30E4-40CF-829A-93F526E2AED8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.DataProtection.EntityFrameworkCore", "modules\data-protection\LINGYUN.Abp.DataProtection.EntityFrameworkCore\LINGYUN.Abp.DataProtection.EntityFrameworkCore.csproj", "{E16CCB14-E629-48E6-9603-53BBFF185318}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.DataProtection.Tests", "tests\LINGYUN.Abp.DataProtection.Tests\LINGYUN.Abp.DataProtection.Tests.csproj", "{FBE7D8CB-1D99-4342-A953-B9AB46E0B14D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.EntityFrameworkCore.Tests", "tests\LINGYUN.Abp.EntityFrameworkCore.Tests\LINGYUN.Abp.EntityFrameworkCore.Tests.csproj", "{2F556889-006C-4A9C-8CA3-E31200C06FC9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -472,6 +482,22 @@ Global {3993A315-B250-4C5D-98C7-90FD06841B66}.Debug|Any CPU.Build.0 = Debug|Any CPU {3993A315-B250-4C5D-98C7-90FD06841B66}.Release|Any CPU.ActiveCfg = Release|Any CPU {3993A315-B250-4C5D-98C7-90FD06841B66}.Release|Any CPU.Build.0 = Release|Any CPU + {519BF5DA-30E4-40CF-829A-93F526E2AED8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {519BF5DA-30E4-40CF-829A-93F526E2AED8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {519BF5DA-30E4-40CF-829A-93F526E2AED8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {519BF5DA-30E4-40CF-829A-93F526E2AED8}.Release|Any CPU.Build.0 = Release|Any CPU + {E16CCB14-E629-48E6-9603-53BBFF185318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E16CCB14-E629-48E6-9603-53BBFF185318}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E16CCB14-E629-48E6-9603-53BBFF185318}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E16CCB14-E629-48E6-9603-53BBFF185318}.Release|Any CPU.Build.0 = Release|Any CPU + {FBE7D8CB-1D99-4342-A953-B9AB46E0B14D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBE7D8CB-1D99-4342-A953-B9AB46E0B14D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBE7D8CB-1D99-4342-A953-B9AB46E0B14D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBE7D8CB-1D99-4342-A953-B9AB46E0B14D}.Release|Any CPU.Build.0 = Release|Any CPU + {2F556889-006C-4A9C-8CA3-E31200C06FC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F556889-006C-4A9C-8CA3-E31200C06FC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F556889-006C-4A9C-8CA3-E31200C06FC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F556889-006C-4A9C-8CA3-E31200C06FC9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -563,6 +589,11 @@ Global {CCB4AE25-7059-4CA0-A3AB-CBB863A3F672} = {23F4260D-87C1-4AA6-A302-0A8A76D53BA1} {4635BDFB-B647-43E2-BAA5-D3C17899AF24} = {E73A0F8B-2B4B-4CED-82A4-1EE5E0B89744} {3993A315-B250-4C5D-98C7-90FD06841B66} = {086BE5BE-8594-4DA7-8819-935FEF76DABD} + {A0910407-CE69-4DC8-9721-F4324C22EEA8} = {02EA4E78-5891-43BC-944F-3E52FEE032E4} + {519BF5DA-30E4-40CF-829A-93F526E2AED8} = {A0910407-CE69-4DC8-9721-F4324C22EEA8} + {E16CCB14-E629-48E6-9603-53BBFF185318} = {A0910407-CE69-4DC8-9721-F4324C22EEA8} + {FBE7D8CB-1D99-4342-A953-B9AB46E0B14D} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} + {2F556889-006C-4A9C-8CA3-E31200C06FC9} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {06C707C6-02C0-411A-AD3B-2D0E13787CB8} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN.Abp.DataProtection.EntityFrameworkCore.csproj b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN.Abp.DataProtection.EntityFrameworkCore.csproj new file mode 100644 index 000000000..c60190a4c --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN.Abp.DataProtection.EntityFrameworkCore.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.1 + 9.0 + + + + + + + + + + + + diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/AbpDataProtectionDbContext.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/AbpDataProtectionDbContext.cs new file mode 100644 index 000000000..a4435d0b7 --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/AbpDataProtectionDbContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace LINGYUN.Abp.DataProtection.EntityFrameworkCore +{ + public class AbpDataProtectionDbContext : AbpDbContext + where TDbContext : DbContext + { + public AbpDataProtectionDbContext( + DbContextOptions options) : base(options) + { + } + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/AbpDataProtectionEntityFrameworkCoreModule.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/AbpDataProtectionEntityFrameworkCoreModule.cs new file mode 100644 index 000000000..3ae596466 --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/AbpDataProtectionEntityFrameworkCoreModule.cs @@ -0,0 +1,12 @@ +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.DataProtection.EntityFrameworkCore +{ + [DependsOn( + typeof(AbpDataProtectionModule), + typeof(AbpEntityFrameworkCoreModule))] + public class AbpDataProtectionEntityFrameworkCoreModule : AbpModule + { + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/DataProtectionAsyncQueryableProvider.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/DataProtectionAsyncQueryableProvider.cs new file mode 100644 index 000000000..4edbe4d0d --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/DataProtectionAsyncQueryableProvider.cs @@ -0,0 +1,358 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query.Internal; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Linq; + +namespace LINGYUN.Abp.DataProtection.EntityFrameworkCore +{ + /// + /// TODO: 需要实现动态数据权限规则 + /// + [Dependency(ReplaceServices = true)] + public class DataProtectionAsyncQueryableProvider : IAsyncQueryableProvider, ISingletonDependency + { + public bool CanExecute(IQueryable queryable) + { + return queryable.Provider is EntityQueryProvider; + } + + public Task ContainsAsync(IQueryable queryable, T item, CancellationToken cancellationToken = default) + { + return queryable.ContainsAsync(item, cancellationToken); + } + + public Task AnyAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AnyAsync(cancellationToken); + } + + public Task AnyAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + return queryable.AnyAsync(predicate, cancellationToken); + } + + public Task AllAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + return queryable.AllAsync(predicate, cancellationToken); + } + + public Task CountAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.CountAsync(cancellationToken); + } + + public Task CountAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + return queryable.CountAsync(predicate, cancellationToken); + } + + public Task LongCountAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.LongCountAsync(cancellationToken); + } + + public Task LongCountAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + return queryable.LongCountAsync(predicate, cancellationToken); + } + + public Task FirstAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.FirstAsync(cancellationToken); + } + + public Task FirstAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + return queryable.FirstAsync(predicate, cancellationToken); + } + + public Task FirstOrDefaultAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.FirstOrDefaultAsync(cancellationToken); + } + + public Task FirstOrDefaultAsync(IQueryable queryable, Expression> predicate, + CancellationToken cancellationToken = default) + { + return queryable.FirstOrDefaultAsync(predicate, cancellationToken); + } + + public Task LastAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.LastAsync(cancellationToken); + } + + public Task LastAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + return queryable.LastAsync(predicate, cancellationToken); + } + + public Task LastOrDefaultAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.LastOrDefaultAsync(cancellationToken); + } + + public Task LastOrDefaultAsync(IQueryable queryable, Expression> predicate, + CancellationToken cancellationToken = default) + { + return queryable.LastOrDefaultAsync(predicate, cancellationToken); + } + + public Task SingleAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SingleAsync(cancellationToken); + } + + public Task SingleAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + return queryable.SingleAsync(predicate, cancellationToken); + } + + public Task SingleOrDefaultAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SingleOrDefaultAsync(cancellationToken); + } + + public Task SingleOrDefaultAsync(IQueryable queryable, Expression> predicate, + CancellationToken cancellationToken = default) + { + return queryable.SingleOrDefaultAsync(predicate, cancellationToken); + } + + public Task MinAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.MinAsync(cancellationToken); + } + + public Task MinAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.MinAsync(selector, cancellationToken); + } + + public Task MaxAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.MaxAsync(cancellationToken); + } + + public Task MaxAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.MaxAsync(selector, cancellationToken); + } + + public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(cancellationToken); + } + + public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(cancellationToken); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(selector, cancellationToken); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(selector, cancellationToken); + } + + public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(cancellationToken); + } + + public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(cancellationToken); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(selector, cancellationToken); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(selector, cancellationToken); + } + + public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(cancellationToken); + } + + public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(cancellationToken); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(selector, cancellationToken); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(selector, cancellationToken); + } + + public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(cancellationToken); + } + + public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(cancellationToken); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(selector, cancellationToken); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(selector, cancellationToken); + } + + public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(cancellationToken); + } + + public Task SumAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(cancellationToken); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(selector, cancellationToken); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.SumAsync(selector, cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(selector, cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(selector, cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(selector, cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(selector, cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(selector, cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(selector, cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(selector, cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(selector, cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(selector, cancellationToken); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + return queryable.AverageAsync(selector, cancellationToken); + } + + public Task> ToListAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.ToListAsync(cancellationToken); + } + + public Task ToArrayAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.ToArrayAsync(cancellationToken); + } + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/EfCoreDataProtectionRepositoryBase.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/EfCoreDataProtectionRepositoryBase.cs new file mode 100644 index 000000000..9ea99a12b --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/EfCoreDataProtectionRepositoryBase.cs @@ -0,0 +1,297 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Users; + +namespace LINGYUN.Abp.DataProtection.EntityFrameworkCore +{ + /// + /// 受保护的资源仓储接口需要继承此接口,否则需要自行实现过滤器 + /// + /// + /// + /// + public abstract class EfCoreDataProtectionRepositoryBase : EfCoreRepository + where TEntity : class, IEntity, IDataProtection + where TDbContext: IEfCoreDbContext + { + protected ICurrentUser CurrentUser => LazyServiceProvider.LazyGetService(); + protected IDataProtectdChecker DataProtectdChecker => LazyServiceProvider.LazyGetService(); + protected EfCoreDataProtectionRepositoryBase( + IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public override async Task InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) + { + if (CurrentUser.IsAuthenticated && + entity is IDataProtection protectedEntity) + { + ProtectedEntityHelper.TrySetOwner( + protectedEntity, + () => string.Join(",", CurrentUser.UserName, CurrentUser.Roles.JoinAsString(","))); + } + + return await base.InsertAsync(entity, autoSave, cancellationToken); + } + + public override async Task InsertManyAsync(IEnumerable entities, bool autoSave = false, CancellationToken cancellationToken = default) + { + if (CurrentUser.IsAuthenticated && + typeof(IDataProtection).IsAssignableFrom(typeof(TEntity))) + { + foreach (var entity in entities) + { + ProtectedEntityHelper.TrySetOwner( + entity, + () => string.Join(",", CurrentUser.UserName, CurrentUser.Roles.JoinAsString(","))); + } + } + + await base.InsertManyAsync(entities, autoSave, cancellationToken); + } + + public override async Task> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default) + { + return includeDetails + ? await(await WithDetailsAsync(ProtectBehavior.Query)).ToListAsync(GetCancellationToken(cancellationToken)) + : await(await GetQueryableAsync(ProtectBehavior.Query)).ToListAsync(GetCancellationToken(cancellationToken)); + } + + public override async Task FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default) + { + return includeDetails + ? await(await WithDetailsAsync(ProtectBehavior.Query)).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken)) + : await(await GetQueryableAsync(ProtectBehavior.Query)).OrderBy(e => e.Id).FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken)); + } + + public override async Task> GetListAsync( + Expression> predicate, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return includeDetails + ? await (await WithDetailsAsync(ProtectBehavior.Query)).Where(predicate).ToListAsync(GetCancellationToken(cancellationToken)) + : await (await GetQueryableAsync(ProtectBehavior.Query)).Where(predicate).ToListAsync(GetCancellationToken(cancellationToken)); + } + + public override async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default) + { + var queryable = await GetQueryableAsync(ProtectBehavior.Delete); + var entity = await queryable + .FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken)); + if (entity == null) + { + return; + } + await DeleteAsync(entity, autoSave, cancellationToken); + } + + public override async Task DeleteManyAsync(IEnumerable ids, bool autoSave = false, CancellationToken cancellationToken = default) + { + var queryable = await GetQueryableAsync(ProtectBehavior.Delete); + + var entities = await queryable.Where(x => ids.Contains(x.Id)).ToListAsync(cancellationToken); + + await DeleteManyAsync(entities, autoSave, cancellationToken); + } + + public override async Task DeleteAsync( + Expression> predicate, + bool autoSave = false, + CancellationToken cancellationToken = default) + { + var queryable = await GetQueryableAsync(ProtectBehavior.Delete); + + var entities = await queryable.Where(predicate).ToListAsync(cancellationToken); + + await DeleteManyAsync(entities, autoSave, cancellationToken); + } + protected virtual async Task> WithDetailsAsync(ProtectBehavior behavior = ProtectBehavior.All) + { + if (typeof(IDataProtection).IsAssignableFrom(typeof(TEntity))) + { + var result = await DataProtectdChecker.IsGrantedAsync(behavior); + if (!result.Succeeded) + { + var queryable = await base.GetQueryableAsync(); + return queryable.Where((t) => false); + } + + return await WithDetailsAsync(result); + } + + return await base.WithDetailsAsync(); + } + protected virtual async Task> WithDetailsAsync(ResourceGrantedResult resourceGranted) + { + if (AbpEntityOptions.DefaultWithDetailsFunc == null) + { + return await GetQueryableAsync(resourceGranted); + } + + return AbpEntityOptions.DefaultWithDetailsFunc(await GetQueryableAsync(resourceGranted)); + } + protected virtual async Task> GetQueryableAsync(ProtectBehavior behavior = ProtectBehavior.All) + { + if (typeof(IDataProtection).IsAssignableFrom(typeof(TEntity))) + { + var result = await DataProtectdChecker.IsGrantedAsync(behavior); + if (!result.Succeeded) + { + var queryable = await base.GetQueryableAsync(); + return queryable.Where((t) => false); + } + + return await GetQueryableAsync(result); + } + + return await base.GetQueryableAsync(); + } + protected virtual async Task> GetQueryableAsync(ResourceGrantedResult resourceGranted) + { + var queryable = await base.GetQueryableAsync(); + if (!resourceGranted.Succeeded) + { + // 无资源访问权限, 不返回结果 + return queryable.Where((t) => false); + } + // 资源过滤,用户是否有对某个资源的访问权限 + // 方案1、Resource.Owner In ("user1", "user2", "role1", "role2", "organization1", "...") 独立模块,业务增加Owner字段 + // 方案2、Select R.* From Resource R Inner Join Protect T On T.Visitor = R.Owner Where T.Resource = 'Resource' 业务侵入,增加Protect表 + queryable = FilterResource(queryable, resourceGranted.Resource); + // 对于可访问资源的进一步动态规则过滤 1 == 1 And Resource.Field1 = 'allow' And Resource.Field2 >= 100 And Resource.Field2 <= 200 + queryable = FilterFieldRule(queryable, resourceGranted.Rules); + // 对于资源可访问字段过滤 Select Resource.Field1, Resource.Field2, Resource.Field3 + queryable = FilterFieldReturn(queryable, resourceGranted.Fields); + + return queryable; + } + + protected virtual IQueryable FilterResource(IQueryable queryable, ProtectedResource resource) + where T : IDataProtection + { + ParameterExpression pe = Expression.Parameter(typeof(T)); + + // 检查资源的可访问者 + // any: 内置常量,允许访问所有资源 + if (!resource.Visitor.IsNullOrWhiteSpace() || !resource.Visitor.Contains("any")) + { + // 过滤允许的资源访问者 + // 方案一:模块之间独立,传递当前访问者即可 + // Select * From Resource As R Where R.Owner LIKE ('visitor1', 'visitorRole1') + var ownerExp = Expression.PropertyOrField(pe, nameof(IDataProtection.Owner)); + var visities = resource.Visitor.Split(','); + Expression visitorExpr = null; + foreach (var visitor in visities) + { + visitorExpr = visitorExpr == null + ? Expression.Call( + ownerExp, + typeof(string).GetMethod(nameof(string.Contains), new Type[] { typeof(string) }), + Expression.Constant(visitor, ownerExp.Type)) + : Expression.Or( + visitorExpr, + Expression.Call( + ownerExp, + typeof(string).GetMethod(nameof(string.Contains), new Type[] { typeof(string) }), + Expression.Constant(visitor, ownerExp.Type))); + } + + // 方案二:节省网络带宽,快速查询 + // Select R.* From Resource As R + // Inner Join Protect As P On P.Resource = 'Resource' + // Where 1 == 1 + // And P.Behavior = ProtectBehavior.Query + // And ((P.Visitor = 'visitor1') Or (P.Visitor = 'visitorRole1') Or (P.Visitor = 'visitorRole2')) + queryable = queryable.Where(Expression.Lambda>(visitorExpr, pe)); + } + + return queryable; + } + + protected virtual IQueryable FilterFieldRule(IQueryable queryable, IEnumerable rules) + where T : IDataProtection + { + ParameterExpression pe = Expression.Parameter(typeof(T)); + + // 默认未指定访问规则 + // 则可访问所有允许的资源 + if (rules.Any()) + { + Expression> where = PredicateBuilder.New((t) => true); + foreach (var fieldRule in rules) + { + var memberExp = Expression.PropertyOrField(pe, fieldRule.Field); + Expression memberCondition = null; + memberCondition = fieldRule.Operator switch + { + // LIKE + ExpressionType.Contains => Expression.Call( + memberExp, + typeof(string).GetMethod(nameof(string.Contains), new Type[] { typeof(string) }), + Expression.Constant(fieldRule.Value, memberExp.Type)), + // == + ExpressionType.Equal => Expression.Equal(memberExp, Expression.Constant(fieldRule.Value, memberExp.Type)), + // < + ExpressionType.LessThan => Expression.LessThan(memberExp, Expression.Constant(fieldRule.Value, memberExp.Type)), + // <= + ExpressionType.LessThanOrEqual => Expression.LessThanOrEqual(memberExp, Expression.Constant(fieldRule.Value, memberExp.Type)), + // > + ExpressionType.GreaterThan => Expression.GreaterThan(memberExp, Expression.Constant(fieldRule.Value, memberExp.Type)), + // >= + ExpressionType.GreaterThanOrEqual => Expression.GreaterThanOrEqual(memberExp, Expression.Constant(fieldRule.Value, memberExp.Type)), + // 其他操作符未引入 + _ => throw new NotSupportedException($"Dynamic rules do not support operator: {fieldRule.Operator}"), + }; + switch (fieldRule.Logic) + { + case PredicateOperator.And: + where = where.And(Expression.Lambda>(memberCondition, pe)); + break; + case PredicateOperator.Or: + where = where.Or(Expression.Lambda>(memberCondition, pe)); + break; + } + } + queryable = queryable.Where(where); + } + + return queryable; + } + + protected virtual IQueryable FilterFieldReturn(IQueryable queryable, IEnumerable fields) + { + // 默认未指定可访问字段则返回所有字段 + if (fields.Any()) + { + ParameterExpression pe = Expression.Parameter(typeof(T)); + Type queryableResultType = typeof(T); + NewExpression ne = Expression.New(queryableResultType); + List members = new List(); + + foreach (var field in fields) + { + var fieldProp = queryableResultType.GetProperty(field.Field); + var meField = Expression.MakeMemberAccess(pe, fieldProp); + members.Add(Expression.Bind(fieldProp, meField)); + } + + var mie = Expression.MemberInit(ne, members); + Expression> personSelectExpression = Expression.Lambda>(mie, pe); + + queryable = queryable.Select(personSelectExpression); + } + + return queryable; + } + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/ProtectedEntityHelper.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/ProtectedEntityHelper.cs new file mode 100644 index 000000000..9b852181e --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/ProtectedEntityHelper.cs @@ -0,0 +1,19 @@ +using System; +using Volo.Abp; + +namespace LINGYUN.Abp.DataProtection.EntityFrameworkCore +{ + public static class ProtectedEntityHelper + { + public static void TrySetOwner( + IDataProtection protectedEntity, + Func ownerFactory) + { + ObjectHelper.TrySetProperty( + protectedEntity, + x => x.Owner, + ownerFactory, + new Type[] { }); + } + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN.Abp.DataProtection.csproj b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN.Abp.DataProtection.csproj new file mode 100644 index 000000000..918066b48 --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN.Abp.DataProtection.csproj @@ -0,0 +1,14 @@ + + + + + + netstandard2.0 + + + + + + + + diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/AbpDataProtectionModule.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/AbpDataProtectionModule.cs new file mode 100644 index 000000000..b7b04bbad --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/AbpDataProtectionModule.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Modularity; +using Volo.Abp.Threading; + +namespace LINGYUN.Abp.DataProtection +{ + [DependsOn( + typeof(AbpThreadingModule))] + public class AbpDataProtectionModule : AbpModule + { + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/DataProtectionAsyncQueryableProvider.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/DataProtectionAsyncQueryableProvider.cs new file mode 100644 index 000000000..542fbaf09 --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/DataProtectionAsyncQueryableProvider.cs @@ -0,0 +1,352 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Linq; + +namespace LINGYUN.Abp.DataProtection +{ + public class DataProtectionAsyncQueryableProvider : IAsyncQueryableProvider + { + public Task AllAsync( + IQueryable queryable, + Expression> predicate, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AnyAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AnyAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task AverageAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public bool CanExecute(IQueryable queryable) + { + throw new NotImplementedException(); + } + + public Task ContainsAsync(IQueryable queryable, T item, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task CountAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task CountAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task FirstAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task FirstAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task FirstOrDefaultAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task FirstOrDefaultAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task LastAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task LastAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task LastOrDefaultAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task LastOrDefaultAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task LongCountAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task LongCountAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task MaxAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task MaxAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task MinAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task MinAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SingleAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SingleAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SingleOrDefaultAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SingleOrDefaultAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable source, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task SumAsync(IQueryable queryable, Expression> selector, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task ToArrayAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task> ToListAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ExpressionType.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ExpressionType.cs new file mode 100644 index 000000000..b0d3b7cd8 --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ExpressionType.cs @@ -0,0 +1,12 @@ +namespace LINGYUN.Abp.DataProtection +{ + public enum ExpressionType + { + Contains, + Equal, + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual, + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/IDataProtectdChecker.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/IDataProtectdChecker.cs new file mode 100644 index 000000000..faf748a6f --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/IDataProtectdChecker.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.DataProtection +{ + /// + /// 实现此接口 + /// 检查资源的访问权限 + /// + public interface IDataProtectdChecker + { + /// + /// 资源是否拥有某种行为的访问权限 + /// + /// 受保护的资源(实体) + /// 访问行为 + /// 不管是否拥有访问权限,请返回非空结果,由EF模块检查 + Task IsGrantedAsync(ProtectBehavior behavior = ProtectBehavior.All); + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/IDataProtection.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/IDataProtection.cs new file mode 100644 index 000000000..6b14f607d --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/IDataProtection.cs @@ -0,0 +1,11 @@ +namespace LINGYUN.Abp.DataProtection +{ + /// + /// 实现接口 + /// 数据访问保护 + /// + public interface IDataProtection + { + string Owner { get; } + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectBehavior.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectBehavior.cs new file mode 100644 index 000000000..2916f8869 --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectBehavior.cs @@ -0,0 +1,29 @@ +namespace LINGYUN.Abp.DataProtection +{ + /// + /// 保护行为 + /// + public enum ProtectBehavior + { + /// + /// 所有 + /// + All, + /// + /// 查询 + /// + Query, + /// + /// 新增 + /// + Insert, + /// + /// 修改 + /// + Update, + /// + /// 删除 + /// + Delete + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectedField.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectedField.cs new file mode 100644 index 000000000..6fee9c6b7 --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectedField.cs @@ -0,0 +1,22 @@ +namespace LINGYUN.Abp.DataProtection +{ + public class ProtectedField + { + /// + /// 资源 + /// + public string Resource { get; set; } + /// + /// 资源拥有者 + /// + public string Owner { get; set; } + /// + /// 资源访问者 + /// + public string Visitor { get; set; } + /// + /// 字段 + /// + public string Field { get; set; } + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectedFieldRule.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectedFieldRule.cs new file mode 100644 index 000000000..d1f7e63bf --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectedFieldRule.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace LINGYUN.Abp.DataProtection +{ + public class ProtectedFieldRule + { + /// + /// 资源 + /// + public string Resource { get; set; } + /// + /// 资源拥有者 + /// + public string Owner { get; set; } + /// + /// 资源访问者 + /// + public string Visitor { get; set; } + /// + /// 字段 + /// + public string Field { get; set; } + /// + /// 值 + /// + public object Value { get; set; } + /// + /// 连接类型 + /// Or 或 + /// And 且 + /// + public PredicateOperator Logic { get; set; } + /// + /// 操作符 + /// + public ExpressionType Operator { get; set; } + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectedResource.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectedResource.cs new file mode 100644 index 000000000..bbdc6dcbb --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ProtectedResource.cs @@ -0,0 +1,27 @@ +namespace LINGYUN.Abp.DataProtection +{ + public class ProtectedResource + { + /// + /// 资源 + /// + public string Resource { get; set; } + /// + /// 资源拥有者 + /// + public string Owner { get; set; } + /// + /// 资源访问者 + /// + public string Visitor { get; set; } + /// + /// 优先级 + /// 值越大排名越靠前 + /// + public int Priority { get; set; } + /// + /// 行为 + /// + public ProtectBehavior Behavior { get; set; } + } +} diff --git a/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ResourceGrantedResult.cs b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ResourceGrantedResult.cs new file mode 100644 index 000000000..805597e21 --- /dev/null +++ b/aspnet-core/modules/data-protection/LINGYUN.Abp.DataProtection/LINGYUN/Abp/DataProtection/ResourceGrantedResult.cs @@ -0,0 +1,19 @@ +namespace LINGYUN.Abp.DataProtection +{ + public class ResourceGrantedResult + { + public bool Succeeded => Resource != null; + public ProtectedResource Resource { get; } + public ProtectedField[] Fields { get; } + public ProtectedFieldRule[] Rules { get; } + public ResourceGrantedResult( + ProtectedResource resource, + ProtectedField[] fields, + ProtectedFieldRule[] rules) + { + Resource = resource; + Fields = fields; + Rules = rules; + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN.Abp.DataProtection.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN.Abp.DataProtection.Tests.csproj new file mode 100644 index 000000000..3e4d7c054 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN.Abp.DataProtection.Tests.csproj @@ -0,0 +1,28 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/AbpDataProtectionTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/AbpDataProtectionTestBase.cs new file mode 100644 index 000000000..c5863f036 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/AbpDataProtectionTestBase.cs @@ -0,0 +1,9 @@ +using LINGYUN.Abp.Tests; + +namespace LINGYUN.Abp.DataProtection +{ + public abstract class AbpDataProtectionTestBase : AbpTestsBase + { + + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/AbpDataProtectionTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/AbpDataProtectionTestModule.cs new file mode 100644 index 000000000..6c503e4ca --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/AbpDataProtectionTestModule.cs @@ -0,0 +1,21 @@ +using LINGYUN.Abp.DataProtection.EntityFrameworkCore; +using LINGYUN.Abp.EntityFrameworkCore.Tests; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.DataProtection +{ + [DependsOn( + typeof(AbpDataProtectionEntityFrameworkCoreModule), + typeof(AbpEntityFrameworkCoreTestModule))] + public class AbpDataProtectionTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(options => + { + options.AddRepository(); + }); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/EfCoreFakeProtectionObjectRepository.cs b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/EfCoreFakeProtectionObjectRepository.cs new file mode 100644 index 000000000..3e071dfc0 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/EfCoreFakeProtectionObjectRepository.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.DataProtection.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace LINGYUN.Abp.DataProtection +{ + public class EfCoreFakeProtectionObjectRepository : + EfCoreDataProtectionRepositoryBase, + IFakeProtectionObjectRepository + { + public EfCoreFakeProtectionObjectRepository( + IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/FakeDataProtectdChecker.cs b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/FakeDataProtectdChecker.cs new file mode 100644 index 000000000..9d1d7b608 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/FakeDataProtectdChecker.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.DataProtection +{ + public class FakeDataProtectdChecker : IDataProtectdChecker, ISingletonDependency + { + private readonly IUnitOfWorkManager _unitOfWorkManager; + + public FakeDataProtectdChecker(IUnitOfWorkManager unitOfWorkManager) + { + _unitOfWorkManager = unitOfWorkManager; + } + + public virtual Task IsGrantedAsync(ProtectBehavior behavior = ProtectBehavior.All) + { + var cacheItem = _unitOfWorkManager.Current.Items["ResourceGranted"]; + var result = cacheItem.As(); + + return Task.FromResult(result); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/FakeDataProtectedDbContext.cs b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/FakeDataProtectedDbContext.cs new file mode 100644 index 000000000..d247afb82 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/FakeDataProtectedDbContext.cs @@ -0,0 +1,24 @@ +using LINGYUN.Abp.DataProtection.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace LINGYUN.Abp.DataProtection +{ + public class FakeDataProtectedDbContext : AbpDataProtectionDbContext + { + public FakeDataProtectedDbContext( + DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(b => + { + b.Property(p => p.Owner).HasColumnName(nameof(IDataProtection.Owner)).HasMaxLength(200); + }); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/FakeProtectionObject.cs b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/FakeProtectionObject.cs new file mode 100644 index 000000000..14c2ab767 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/FakeProtectionObject.cs @@ -0,0 +1,16 @@ +using Volo.Abp.Domain.Entities; + +namespace LINGYUN.Abp.DataProtection +{ + public class FakeProtectionObject : Entity, IDataProtection + { + public virtual string Owner { get; set; } + public virtual string Protect1 { get; set; } + public virtual string Protect2 { get; set; } + public virtual string Value1 { get; set; } + public virtual string Value2 { get; set; } + public virtual int ProtectNum1 { get; set; } + public virtual int ProtectNum2 { get; set; } + public virtual int Num3 { get; set; } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/IFakeProtectionObjectRepository.cs b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/IFakeProtectionObjectRepository.cs new file mode 100644 index 000000000..635746ee8 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/IFakeProtectionObjectRepository.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Domain.Repositories; + +namespace LINGYUN.Abp.DataProtection +{ + public interface IFakeProtectionObjectRepository : IBasicRepository + { + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/ProtectionFieldTests.cs b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/ProtectionFieldTests.cs new file mode 100644 index 000000000..6bc1c83c8 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.DataProtection.Tests/LINGYUN/Abp/DataProtection/ProtectionFieldTests.cs @@ -0,0 +1,297 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Volo.Abp.Security.Claims; +using System.Security.Claims; +using System; +using Shouldly; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.DataProtection +{ + public class ProtectionFieldTests : AbpDataProtectionTestBase + { + private readonly IFakeProtectionObjectRepository _repository; + private readonly ICurrentPrincipalAccessor _accessor; + + public ProtectionFieldTests() + { + _repository = GetRequiredService(); + _accessor = GetRequiredService(); + } + + [Fact] + public virtual async Task FakeAsync() + { + + var values = new List() + { + new FakeProtectionObject + { + Protect1 = "Protect1", + Protect2 = "Protect1", + Value1 = "Value1", + Value2 = "Value1", + ProtectNum1 = 100, + ProtectNum2 = 200, + Num3 = 400 + }, + new FakeProtectionObject + { + Protect1 = "test", + Protect2 = "Protect2", + Value1 = "Value2", + Value2 = "Value2", + ProtectNum1 = 1000, + ProtectNum2 = 2000, + Num3 = 3000 + }, + new FakeProtectionObject + { + Protect1 = "test1", + Protect2 = "Protect3", + Value1 = "Value3", + Value2 = "Value3", + ProtectNum1 = 10000, + ProtectNum2 = 20000, + Num3 = 300 + }, + new FakeProtectionObject + { + Protect1 = "test3", + Protect2 = "Protect4", + Value1 = "Value4", + Value2 = "Value4", + ProtectNum1 = 10000, + ProtectNum2 = 20000, + Num3 = 300 + } + }; + + await WithUnitOfWorkAsync(async () => + { + var resource = new ProtectedResource + { + Resource = typeof(FakeProtectionObject).FullName, + Behavior = ProtectBehavior.All, + Owner = "user1", + Priority = 10, + Visitor = "user1,role1" + }; + + var fields = new List() + { + new ProtectedField + { + Field = nameof(FakeProtectionObject.Num3), + Owner = "user1", + Resource = resource.Resource, + Visitor = "", + }, + new ProtectedField + { + Field = nameof(FakeProtectionObject.Value1), + Owner = "user2", + Resource = resource.Resource, + Visitor = "", + }, + new ProtectedField + { + Field = nameof(FakeProtectionObject.Value2), + Owner = "user1", + Resource = resource.Resource, + Visitor = "", + }, + new ProtectedField + { + Field = nameof(FakeProtectionObject.Protect1), + Owner = "role1", + Resource = resource.Resource, + Visitor = "", + }, + }; + + var rules = new List() + { + new ProtectedFieldRule + { + Field = nameof(FakeProtectionObject.Protect1), + Logic = PredicateOperator.And, + Operator = ExpressionType.Equal, + Resource = resource.Resource, + Value = "test" + }, + new ProtectedFieldRule + { + Field = nameof(FakeProtectionObject.Num3), + Logic = PredicateOperator.Or, + Operator = ExpressionType.LessThanOrEqual, + Resource = resource.Resource, + Value = 300 + }, + }; + + var unitOfWorkManager = GetRequiredService(); + unitOfWorkManager.Current.AddItem( + "ResourceGranted", + new ResourceGrantedResult( + resource, + fields.ToArray(), + rules.ToArray())); + + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(AbpClaimTypes.UserId, Guid.NewGuid().ToString())); + identity.AddClaim(new Claim(AbpClaimTypes.UserName, "user1")); + identity.AddClaim(new Claim(AbpClaimTypes.Role, "role1")); + identity.AddClaim(new Claim(AbpClaimTypes.Role, "role2")); + using (_accessor.Change(new ClaimsPrincipal(identity))) + { + await _repository.InsertManyAsync(values, true); + } + }); + + await WithUnitOfWorkAsync(async () => + { + var resource = new ProtectedResource + { + Resource = typeof(FakeProtectionObject).FullName, + Behavior = ProtectBehavior.All, + Owner = "user1", + Priority = 10, + Visitor = "user1,role1" + }; + + var fields = new List() + { + new ProtectedField + { + Field = nameof(FakeProtectionObject.Num3), + Owner = "user1", + Resource = resource.Resource, + Visitor = "", + }, + new ProtectedField + { + Field = nameof(FakeProtectionObject.Value1), + Owner = "user2", + Resource = resource.Resource, + Visitor = "", + }, + new ProtectedField + { + Field = nameof(FakeProtectionObject.Value2), + Owner = "user1", + Resource = resource.Resource, + Visitor = "", + }, + new ProtectedField + { + Field = nameof(FakeProtectionObject.Protect1), + Owner = "role1", + Resource = resource.Resource, + Visitor = "", + }, + }; + + var rules = new List() + { + new ProtectedFieldRule + { + Field = nameof(FakeProtectionObject.Protect1), + Logic = PredicateOperator.And, + Operator = ExpressionType.Equal, + Resource = resource.Resource, + Value = "test" + }, + new ProtectedFieldRule + { + Field = nameof(FakeProtectionObject.Num3), + Logic = PredicateOperator.Or, + Operator = ExpressionType.LessThanOrEqual, + Resource = resource.Resource, + Value = 300 + }, + }; + + var unitOfWorkManager = GetRequiredService(); + unitOfWorkManager.Current.AddItem( + "ResourceGranted", + new ResourceGrantedResult( + resource, + fields.ToArray(), + rules.ToArray())); + + var identity2 = new ClaimsIdentity(); + identity2.AddClaim(new Claim(AbpClaimTypes.UserId, Guid.NewGuid().ToString())); + identity2.AddClaim(new Claim(AbpClaimTypes.UserName, "user2")); + identity2.AddClaim(new Claim(AbpClaimTypes.Role, "role2")); + using (_accessor.Change(identity2)) + { + var result = await _repository.GetListAsync(); + result.Count.ShouldBe(3); + } + }); + + await WithUnitOfWorkAsync(async () => + { + var resource = new ProtectedResource + { + Resource = typeof(FakeProtectionObject).FullName, + Behavior = ProtectBehavior.All, + Priority = 10, + Visitor = "user3" + }; + + var fields = new List() + { + new ProtectedField + { + Field = nameof(FakeProtectionObject.Num3), + Owner = "user1", + Resource = resource.Resource, + Visitor = "", + } + }; + + var rules = new List() + { + new ProtectedFieldRule + { + Field = nameof(FakeProtectionObject.Protect1), + Logic = PredicateOperator.And, + Operator = ExpressionType.Equal, + Resource = resource.Resource, + Value = "test" + }, + new ProtectedFieldRule + { + Field = nameof(FakeProtectionObject.Num3), + Logic = PredicateOperator.Or, + Operator = ExpressionType.LessThanOrEqual, + Resource = resource.Resource, + Value = 300 + }, + }; + + var unitOfWorkManager = GetRequiredService(); + unitOfWorkManager.Current.AddItem( + "ResourceGranted", + new ResourceGrantedResult( + resource, + fields.ToArray(), + rules.ToArray())); + + var identity3 = new ClaimsIdentity(); + identity3.AddClaim(new Claim(AbpClaimTypes.UserId, Guid.NewGuid().ToString())); + identity3.AddClaim(new Claim(AbpClaimTypes.UserName, "user3")); + identity3.AddClaim(new Claim(AbpClaimTypes.Role, "role3")); + using (_accessor.Change(identity3)) + { + var result = await _repository.GetListAsync(); + result.Count.ShouldBe(0); + } + }); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/AbpTestsBase.cs b/aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/AbpTestsBase.cs index fb8afac33..a1b467558 100644 --- a/aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/AbpTestsBase.cs +++ b/aspnet-core/tests/LINGYUN.Abp.TestBase/LINGYUN/Abp/Tests/AbpTestsBase.cs @@ -1,59 +1,59 @@ -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Modularity; -using Volo.Abp.Testing; -using Volo.Abp.Uow; - -namespace LINGYUN.Abp.Tests -{ - public abstract class AbpTestsBase : AbpIntegratedTest - where TStartupModule : IAbpModule - { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - - protected virtual Task WithUnitOfWorkAsync(Func func) - { - return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); - } - - protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func action) - { - using (var scope = ServiceProvider.CreateScope()) - { - var uowManager = scope.ServiceProvider.GetRequiredService(); - - using (var uow = uowManager.Begin(options)) - { - await action(); - - await uow.CompleteAsync(); - } - } - } - - protected virtual Task WithUnitOfWorkAsync(Func> func) - { - return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); - } - - protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func> func) - { - using (var scope = ServiceProvider.CreateScope()) - { - var uowManager = scope.ServiceProvider.GetRequiredService(); - - using (var uow = uowManager.Begin(options)) - { - var result = await func(); - await uow.CompleteAsync(); - return result; - } - } - } - } -} +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Modularity; +using Volo.Abp.Testing; +using Volo.Abp.Uow; + +namespace LINGYUN.Abp.Tests +{ + public abstract class AbpTestsBase : AbpIntegratedTest + where TStartupModule : IAbpModule + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + protected virtual Task WithUnitOfWorkAsync(Func func) + { + return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); + } + + protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func action) + { + using (var scope = ServiceProvider.CreateScope()) + { + var uowManager = scope.ServiceProvider.GetRequiredService(); + + using (var uow = uowManager.Begin(options)) + { + await action(); + + await uow.CompleteAsync(); + } + } + } + + protected virtual Task WithUnitOfWorkAsync(Func> func) + { + return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func); + } + + protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func> func) + { + using (var scope = ServiceProvider.CreateScope()) + { + var uowManager = scope.ServiceProvider.GetRequiredService(); + + using (var uow = uowManager.Begin(options)) + { + var result = await func(); + await uow.CompleteAsync(); + return result; + } + } + } + } +}