/// <summary> /// Creates the filter function for reading, updating or deleting data from an enumeration of models /// </summary> /// <param name="operation">The database operation that is being performed</param> /// <param name="identityService">The identity service to fetch the user from</param> /// <param name="userManager">A userManager to pass to the ACLs</param> /// <param name="dbContext">A dbContext to pass to the ACLs</param> /// <param name="serviceProvider">Service provider to pass to the ACLs</param> /// <typeparam name="TModel">The type of the model to add security to</typeparam> /// <returns>An expression that can be user for the where condition of a linq query</returns> public static Expression <Func <TModel, bool> > CreateSecurityFilter <TModel>( DATABASE_OPERATION operation, IIdentityService identityService, UserManager <User> userManager, LactalisDBContext dbContext, IServiceProvider serviceProvider) where TModel : IOwnerAbstractModel, new() { identityService.RetrieveUserAsync().Wait(); var model = new TModel(); var userGroups = identityService.Groups; var userModelAcls = model.Acls.Where(x => userGroups.Contains(x.Group) || x.IsVisitorAcl); Expression <Func <TModel, bool> > baseRule = _ => false; var filter = Expression.OrElse(baseRule.Body, baseRule.Body); if (!userModelAcls.Any()) { // If we have no rules on this model then we should inherit from the model driven base rule Expression <Func <TModel, bool> > defaultFilter = _ => ALLOW_DEFAULT; filter = Expression.OrElse(filter, defaultFilter.Body); } else { // Otherwise combine the filter on acl with any existing filters var securityContext = new SecurityContext { DbContext = dbContext, UserManager = userManager, Groups = identityService.Groups, ServiceProvider = serviceProvider, }; IEnumerable <Expression <Func <TModel, bool> > > acls = null; switch (operation) { case DATABASE_OPERATION.READ: acls = userModelAcls.Select(acl => acl.GetReadConditions <TModel>(identityService.User, securityContext)); break; case DATABASE_OPERATION.UPDATE: acls = userModelAcls.Select(acl => acl.GetUpdateConditions <TModel>(identityService.User, securityContext)); break; case DATABASE_OPERATION.DELETE: acls = userModelAcls.Select(acl => acl.GetDeleteConditions <TModel>(identityService.User, securityContext)); break; default: break; } filter = acls.Aggregate(filter, (current, expression) => Expression.OrElse(current, expression.Body)); } var param = Expression.Parameter(typeof(TModel), "model"); var replacer = new ParameterReplacer(param); return(Expression.Lambda <Func <TModel, bool> >(replacer.Visit(filter), param)); }
/// <inheritdoc /> public async Task <BooleanObject> ConditionalUpdate <T>( IQueryable <T> models, MemberInitExpression updateMemberInitExpression, CancellationToken cancellation = default) where T : class, IOwnerAbstractModel, new() { var param = Expression.Parameter(typeof(T), "model"); var replacer = new ParameterReplacer(param); var updateFactory = Expression.Lambda <Func <T, T> >(replacer.Visit(updateMemberInitExpression), param); await models.AddUpdateSecurityFiltering(_identityService, _userManager, _dbContext, _serviceProvider).UpdateAsync(updateFactory, cancellation); var errors = await _securityService.CheckDbSecurityChanges(_identityService, _dbContext); if (errors.Any()) { throw new AggregateException(errors.Select(error => new InvalidOperationException(error))); } await _dbContext.SaveChangesAsync(cancellation); return(new BooleanObject { Value = true }); }