Skip to content

Commit

Permalink
Merge pull request #926 from colinin/refactor-data-protected
Browse files Browse the repository at this point in the history
重新定义数据权限结构
  • Loading branch information
colinin authored Feb 17, 2024
2 parents 1f7c93b + a66a513 commit 68c9120
Show file tree
Hide file tree
Showing 34 changed files with 585 additions and 1,099 deletions.
7 changes: 7 additions & 0 deletions aspnet-core/LINGYUN.MicroService.All.sln
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Dynamic.Queryab
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Dynamic.Queryable.HttpApi", "framework\dynamic-queryable\LINGYUN.Abp.Dynamic.Queryable.HttpApi\LINGYUN.Abp.Dynamic.Queryable.HttpApi.csproj", "{014A9583-0EAA-48A4-ACBE-07DC88159E13}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.DataProtection.Tests", "tests\LINGYUN.Abp.DataProtection.Tests\LINGYUN.Abp.DataProtection.Tests.csproj", "{AAC0C407-B4B9-4E90-99FC-2D793AC229D9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1784,6 +1786,10 @@ Global
{014A9583-0EAA-48A4-ACBE-07DC88159E13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{014A9583-0EAA-48A4-ACBE-07DC88159E13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{014A9583-0EAA-48A4-ACBE-07DC88159E13}.Release|Any CPU.Build.0 = Release|Any CPU
{AAC0C407-B4B9-4E90-99FC-2D793AC229D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAC0C407-B4B9-4E90-99FC-2D793AC229D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAC0C407-B4B9-4E90-99FC-2D793AC229D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAC0C407-B4B9-4E90-99FC-2D793AC229D9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -2123,6 +2129,7 @@ Global
{B6D4AADE-3ABA-45E6-9916-2F8798412549} = {4FAE314C-36CB-4E3F-85B7-41D0A428B37D}
{86E85013-7C71-4770-9323-18897A64F5B2} = {4FAE314C-36CB-4E3F-85B7-41D0A428B37D}
{014A9583-0EAA-48A4-ACBE-07DC88159E13} = {4FAE314C-36CB-4E3F-85B7-41D0A428B37D}
{AAC0C407-B4B9-4E90-99FC-2D793AC229D9} = {370D7CD5-1E17-4F3D-BBFA-03429F6D4F2F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,145 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
using Volo.Abp.Users;

namespace LINGYUN.Abp.DataProtection.EntityFrameworkCore
{
public class AbpDataProtectionDbContext<TDbContext> : AbpDbContext<TDbContext>
where TDbContext : DbContext
public class AbpDataProtectionDbContext : AbpDbContext<AbpDataProtectionDbContext>
{
protected ICurrentUser CurrentUser => LazyServiceProvider.LazyGetService<ICurrentUser>();

protected virtual bool IsDataAccessFilterEnabled => DataFilter?.IsEnabled<IHasDataAccess>() ?? false;

public AbpDataProtectionDbContext(
DbContextOptions<TDbContext> options) : base(options)
DbContextOptions<AbpDataProtectionDbContext> options) : base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(IHasDataAccess).IsAssignableFrom(entityType.ClrType))
{
modelBuilder.Entity(entityType.ClrType)
.OwnsOne(entityType.ClrType.FullName, nameof(IHasDataAccess.Owner), ownedNavigationBuilder =>
{
ownedNavigationBuilder.ToJson();
});
}
}
}

protected override void ApplyAbpConceptsForAddedEntity(EntityEntry entry)
{
base.ApplyAbpConceptsForAddedEntity(entry);
if (CurrentUser.IsAuthenticated)
{
if (entry is IHasDataAccess entity)
{
ProtectedEntityHelper.TrySetOwner(
entity,
() => new DataAccessOwner(
CurrentUser.Id,
CurrentUser.Roles,
CurrentUser.FindOrganizationUnits().Select(ou => ou.ToString()).ToArray()));
}
}
}

protected virtual DataAccessRuleInfo AccessRuleInfo => UnitOfWorkManager.Current.GetAccessRuleInfo();

protected override void HandlePropertiesBeforeSave()
{
foreach (var item in ChangeTracker.Entries().ToList())
{
HandleExtraPropertiesOnSave(item);
HandleCheckPropertiesOnSave(item);
if (item.State.IsIn(EntityState.Modified, EntityState.Deleted))
{
UpdateConcurrencyStamp(item);
}
}
}

protected virtual void HandleCheckPropertiesOnSave(EntityEntry entry)
{
// 仅当启用过滤器时检查
if (IsDataAccessFilterEnabled)
{
var entityAccessRules = AccessRuleInfo?.Rules.Where(r => r.EntityTypeFullName == entry.Metadata.ClrType.FullName);
if (entityAccessRules != null)
{
if (entry.State.IsIn(EntityState.Modified, EntityState.Added, EntityState.Deleted))
{
var entityAccessRule = entityAccessRules.FirstOrDefault(r => r.Operation.IsIn(DataAccessOperation.Write, DataAccessOperation.Delete));
if (entityAccessRule != null)
{
if (entityAccessRule.Fileds.Count != 0)
{
var notAccessProps = entry.Properties.Where(p => !entityAccessRule.Fileds.Any(f => f.Field == p.Metadata.Name));
if (notAccessProps != null)
{
foreach (var property in notAccessProps)
{
// 无字段权限不做变更
property.CurrentValue = property.OriginalValue;
}
}
}
}
else
{
// 无实体变更权限不做修改
entry.State = EntityState.Unchanged;
}
}
}
}
}

protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
{
var expression = base.CreateFilterExpression<TEntity>();

if (typeof(IHasDataAccess).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> expression2 = (TEntity e) => !IsDataAccessFilterEnabled || CreateFilterExpression(e, AccessRuleInfo);
expression = (Expression<Func<TEntity, bool>>)((expression == null) ? ((LambdaExpression)expression2) : ((LambdaExpression)QueryFilterExpressionHelper.CombineExpressions(expression, expression2)));
}

return expression;
}

protected static bool CreateFilterExpression<TEntity>(TEntity entity, DataAccessRuleInfo accessRuleInfo)
{
if (accessRuleInfo == null)
{
return true;
}

if (!accessRuleInfo.Rules.Any(r => r.EntityTypeFullName == typeof(TEntity).FullName))
{
return false;
}

if (entity is not IHasDataAccess accessEntity)
{
return true;
}

// TODO: 需要完成详细的过滤条件
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,47 +1,85 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Users;

namespace LINGYUN.Abp.DataProtection.EntityFrameworkCore;
public class DataProtectionQueryExpressionInterceptor : IQueryExpressionInterceptor
{
public Expression QueryCompilationStarting(Expression queryExpression, QueryExpressionEventData eventData)
{
return new DataProtectionExpressionVisitor().Visit(queryExpression);
var dataFilter = eventData.Context.GetService<IDataFilter>();
var dbContextProvider = eventData.Context.GetService<IDbContextProvider<AbpDataProtectionDbContext>>();
return new DataProtectionExpressionVisitor(dataFilter, dbContextProvider).Visit(queryExpression);
}

public class DataProtectionExpressionVisitor : ExpressionVisitor
{
private readonly static MethodInfo WhereMethodInfo = typeof(Queryable).GetMethod(nameof(Queryable.Where));


private readonly IDataFilter _dataFilter;
private readonly ICurrentUser _currentUser;

Check warning on line 33 in aspnet-core/framework/data-protection/LINGYUN.Abp.DataProtection.EntityFrameworkCore/LINGYUN/Abp/DataProtection/EntityFrameworkCore/DataProtectionQueryExpressionInterceptor.cs

View workflow job for this annotation

GitHub Actions / Build

Field 'DataProtectionQueryExpressionInterceptor.DataProtectionExpressionVisitor._currentUser' is never assigned to, and will always have its default value null
private readonly IDbContextProvider<AbpDataProtectionDbContext> _dbContextProvider;

public DataProtectionExpressionVisitor(
IDataFilter dataFilter,
IDbContextProvider<AbpDataProtectionDbContext> dbContextProvider)
{
_dataFilter = dataFilter;
_dbContextProvider = dbContextProvider;
}

protected override Expression VisitMethodCall(MethodCallExpression node)
{
//if (_dataFilter.IsEnabled<IDataProtection>())
//{
// var methodInfo = node!.Method;
// if (methodInfo.DeclaringType == typeof(Queryable)
// && methodInfo.Name == nameof(Queryable.Select)
// && methodInfo.GetParameters().Length == 2)
// {
// var sourceType = node.Type.GetGenericArguments()[0];
// var lambdaExpression = (LambdaExpression)((UnaryExpression)node.Arguments[1]).Operand;
// var entityParameterExpression = lambdaExpression.Parameters[0];
// var test = Expression.Call(
// method: WhereMethodInfo.MakeGenericMethod(sourceType, typeof(bool)),
// arg0: base.VisitMethodCall(node),
// arg1: Expression.Lambda(typeof(Func<,>).MakeGenericType(entityParameterExpression.Type, typeof(bool)),
// Expression.Property(entityParameterExpression, nameof(IDataProtection.Owner)),
// true));
// return test;
// }
//}
if (_dataFilter.IsEnabled<IHasDataAccess>())
{
var methodInfo = node!.Method;
if (methodInfo.DeclaringType == typeof(Queryable)
&& methodInfo.Name == nameof(Queryable.Select)
&& methodInfo.GetParameters().Length == 2)
{
var sourceType = node.Type.GetGenericArguments()[0];
var lambdaExpression = (LambdaExpression)((UnaryExpression)node.Arguments[1]).Operand;
var entityParameterExpression = lambdaExpression.Parameters[0];

var rules = _currentUser.Roles;
var ous = _currentUser.FindOrganizationUnits();

var ownerParamter = Expression.PropertyOrField(entityParameterExpression, nameof(IHasDataAccess.Owner));


if (typeof(IEntity).IsAssignableFrom(sourceType))
{
// Join params[0]
// node




var test = Expression.Call(
method: WhereMethodInfo.MakeGenericMethod(sourceType, typeof(bool)),
arg0: base.VisitMethodCall(node),
arg1: Expression.Lambda(
typeof(Func<,>).MakeGenericType(entityParameterExpression.Type, typeof(bool)),
Expression.Property(entityParameterExpression, nameof(IHasDataAccess.Owner)),
true));
return test;
}

}
}
return base.VisitMethodCall(node);
}
}
Expand Down
Loading

0 comments on commit 68c9120

Please sign in to comment.