protected override Query <InboxRecord> Search(Query <InboxRecord> query, GetArguments args) { string search = args.Search; if (!string.IsNullOrWhiteSpace(search)) { search = search.Replace("'", "''"); // escape quotes by repeating them var createdByProp = nameof(InboxRecord.CreatedBy); var nameProp = $"{createdByProp}.{nameof(User.Name)}"; var name2Prop = $"{createdByProp}.{nameof(User.Name2)}"; var name3Prop = $"{createdByProp}.{nameof(User.Name3)}"; var commentProp = nameof(InboxRecord.Comment); var memoProp = $"{nameof(InboxRecord.Document)}.{nameof(Document.Memo)}"; // Prepare the filter string var filterString = $"{nameProp} contains '{search}' or {name2Prop} contains '{search}' or {name3Prop} contains '{search}' or {commentProp} contains '{search}' or {memoProp} contains '{search}'"; // Apply the filter query = query.Filter(ExpressionFilter.Parse(filterString)); } return(query); }
public NewAmericanCarsQuery() { Filter = new ExpressionFilter <Car>(x => x.Year > 2002).And(new ExpressionFilter <Car>(x => _american.Contains(x.Make))); Sort = new DynamicLinqSort("Model asc"); PageSize = 1; PageNumber = 1; }
/// <summary> /// Compliments <see cref="CheckActionPermissionsBefore(ExpressionFilter, List{TKey})"/> when the user has partial (Row level) access on a table /// This utility method checks (after the action has been perfromed) that the permission criteria predicate is still true for all actioned /// Ids, otherwise throws a <see cref="ForbiddenException"/> to roll back the transaction (the method must be called before committing the transaction /// </summary> protected async Task CheckActionPermissionsAfter(ExpressionFilter actionFilter, List <TKey> actionedIds, List <TEntity> data) { if (actionFilter != null) { // How many of those Ids is the user allowed to apply the action to int actionableIdsCount; if (data != null) { // Optimization, if the data is already loaded by the action handler // (with the action permissions filter applied), count that in memory actionableIdsCount = data.Count; } else { // If data is not loaded, a DB request is necessary to count actionableIdsCount = await GetRepository() .Query <TEntity>() .Select("Id") .Filter(actionFilter) .FilterByIds(actionedIds) .CountAsync(cancellation: default); } // If permitted less than actual => Forbidden if (actionableIdsCount < actionedIds.Count) { throw new ForbiddenException(); } } }
/// <summary> /// Returns an <see cref="List{TEntity}"/> based on a custom <paramref name="filterFunc"/> applied to the query, as well as /// optional <paramref name="expand"/> and <paramref name="select"/> arguments, checking the user's READ permissions along the way. /// </summary> /// <param name="filterFunc">Allows any kind of filtering on the query</param> /// <param name="expand">Optional expand argument.</param> /// <param name="select">Optional select argument.</param> /// <param name="orderby">Optional orderby argument.</param> /// <param name="permissionsFilter">Optional filter argument, if null is passed the query uses the read permissions filter of the current user.</param> /// <param name="cancellation">The cancellation instruction.</param> protected async Task <List <TEntity> > GetEntitiesByCustomQuery( Func <EntityQuery <TEntity>, EntityQuery <TEntity> > filterFunc, ExpressionExpand expand, ExpressionSelect select, ExpressionOrderBy orderby, ExpressionFilter permissionsFilter, CancellationToken cancellation) { // Prepare a query of the result, and clone it var factory = QueryFactory(); var query = factory.EntityQuery <TEntity>(); // Apply custom filter function query = filterFunc(query); // Apply read permissions permissionsFilter ??= await UserPermissionsFilter(PermissionActions.Read, cancellation); query = query.Filter(permissionsFilter); // Expand, Select and Order the result as specified in the Queryex agruments var expandedQuery = query.Expand(expand).Select(select).OrderBy(orderby ?? ExpressionOrderBy.Parse("Id")); // Required // Load the result into memory var data = await expandedQuery.ToListAsync(QueryContext(), cancellation); // this is potentially unordered, should that be a concern? // Return return(data); }
public void TestExtensions_Apply_HasFilter() { var list = new List <QueryableOperatorTestClass>(); list.Add(new QueryableOperatorTestClass { Id = 2 }); list.Add(new QueryableOperatorTestClass { Id = 1 }); list = list.OrderBy(x => x.Id).ToList(); var filterValue = 1; var defaultSorter = new ExpressionSorter <QueryableOperatorTestClass>(x => x.Id, SortDirection.Ascending); var filter = new ExpressionFilter <QueryableOperatorTestClass>(x => x.Id, ComparisonType.Equal, filterValue); var queryOperator = new QueryableOperator <QueryableOperatorTestClass>(0, 10, defaultSorter, new List <IFilter> { filter }, null); var query = list.AsQueryable().Apply(queryOperator); var testList = query.ToList(); Assert.AreEqual(1, testList.Count); Assert.AreEqual(filterValue, testList.First().Id); }
public NewAmericanCarsQuery() { Filter = new ExpressionFilter<Car>(x => x.Year > 2002).And(new ExpressionFilter<Car>(x => _american.Contains(x.Make))); Sort = new DynamicLinqSort("Model asc"); PageSize = 1; PageNumber = 1; }
public PagedResult <LogMessage> GetLogs( LogTimePeriod logTimePeriod, int pageNumber = 1, int pageSize = 100, Direction orderDirection = Direction.Descending, string?filterExpression = null, string[]?logLevels = null) { var expression = new ExpressionFilter(filterExpression); IReadOnlyList <LogEvent> filteredLogs = GetLogs(logTimePeriod, expression, 0, int.MaxValue); // This is user used the checkbox UI to toggle which log levels they wish to see // If an empty array or null - its implied all levels to be viewed if (logLevels?.Length > 0) { var logsAfterLevelFilters = new List <LogEvent>(); var validLogType = true; foreach (var level in logLevels) { // Check if level string is part of the LogEventLevel enum if (Enum.IsDefined(typeof(LogEventLevel), level)) { validLogType = true; logsAfterLevelFilters.AddRange(filteredLogs.Where(x => string.Equals(x.Level.ToString(), level, StringComparison.InvariantCultureIgnoreCase))); } else { validLogType = false; } } if (validLogType) { filteredLogs = logsAfterLevelFilters; } } long totalRecords = filteredLogs.Count; // Order By, Skip, Take & Select IEnumerable <LogMessage> logMessages = filteredLogs .OrderBy(l => l.Timestamp, orderDirection) .Skip(pageSize * (pageNumber - 1)) .Take(pageSize) .Select(x => new LogMessage { Timestamp = x.Timestamp, Level = x.Level, MessageTemplateText = x.MessageTemplate.Text, Exception = x.Exception?.ToString(), Properties = x.Properties, RenderedMessage = x.RenderMessage(), }); return(new PagedResult <LogMessage>(totalRecords, pageNumber, pageSize) { Items = logMessages }); }
/// <summary> /// Loads the expressions. /// </summary> /// <param name="iStartIndex">Start index of the i.</param> /// <param name="iNumItems">The i num items.</param> /// <param name="sFilter">The s filter.</param> private void LoadExpressions(int iStartIndex, int iNumItems, string sFilter) { ExpressionDto dto = ExpressionManager.GetExpressionDto(ExpressionCategory.GetExpressionCategory(ExpressionCategory.CategoryKey.Promotion).Key); ExpressionFilter.DataSource = dto.Expression; ExpressionFilter.DataBind(); }
protected override Task <EntityQuery <OutboxRecord> > Search(EntityQuery <OutboxRecord> query, GetArguments args, CancellationToken _) { string search = args.Search; if (!string.IsNullOrWhiteSpace(search)) { search = search.Replace("'", "''"); // escape quotes by repeating them var assigneeProp = nameof(OutboxRecord.Assignee); var nameProp = $"{assigneeProp}.{nameof(User.Name)}"; var name2Prop = $"{assigneeProp}.{nameof(User.Name2)}"; var name3Prop = $"{assigneeProp}.{nameof(User.Name3)}"; var commentProp = nameof(OutboxRecord.Comment); var memoProp = $"{nameof(OutboxRecord.Document)}.{nameof(Document.Memo)}"; // Prepare the filter string var filterString = $"{nameProp} contains '{search}' or {name2Prop} contains '{search}' or {name3Prop} contains '{search}' or {commentProp} contains '{search}' or {memoProp} contains '{search}'"; // Apply the filter query = query.Filter(ExpressionFilter.Parse(filterString)); } return(Task.FromResult(query)); }
protected override Query <ExchangeRate> Search(Query <ExchangeRate> query, GetArguments args) { string search = args.Search; if (!string.IsNullOrWhiteSpace(search)) { search = search.Replace("'", "''"); // escape quotes by repeating them var currencyProp = nameof(ExchangeRate.Currency); var idProp = $"{currencyProp}.{nameof(Currency.Id)}"; var nameProp = $"{currencyProp}.{nameof(Currency.Name)}"; var name2Prop = $"{currencyProp}.{nameof(Currency.Name2)}"; var name3Prop = $"{currencyProp}.{nameof(Currency.Name3)}"; var descProp = $"{currencyProp}.{nameof(Currency.Description)}"; var desc2Prop = $"{currencyProp}.{nameof(Currency.Description2)}"; var desc3Prop = $"{currencyProp}.{nameof(Currency.Description3)}"; // Prepare the filter string var filterString = $"{idProp} contains '{search}' or {nameProp} contains '{search}' or {name2Prop} contains '{search}' or {name3Prop} contains '{search}' or {descProp} contains '{search}' or {desc2Prop} contains '{search}' or {desc3Prop} contains '{search}'"; // If the search is a date, include documents with that date if (DateTime.TryParse(search.Trim(), out DateTime searchDate)) { var validAsOfProp = nameof(ExchangeRate.ValidAsOf); filterString = $"{filterString} or {validAsOfProp} eq {searchDate:yyyy-MM-dd}"; } // Apply the filter query = query.Filter(ExpressionFilter.Parse(filterString)); } return(query); }
protected override Task <EntityQuery <LineDefinition> > Search(EntityQuery <LineDefinition> query, GetArguments args, CancellationToken _) { string search = args.Search; if (!string.IsNullOrWhiteSpace(search)) { search = search.Replace("'", "''"); // escape quotes by repeating them var titleP = nameof(LookupDefinition.TitlePlural); var titleP2 = nameof(LookupDefinition.TitlePlural2); var titleP3 = nameof(LookupDefinition.TitlePlural3); var titleS = nameof(LookupDefinition.TitleSingular); var titleS2 = nameof(LookupDefinition.TitleSingular2); var titleS3 = nameof(LookupDefinition.TitleSingular3); var code = nameof(LineDefinition.Code); var desc = nameof(LineDefinition.Description); var desc2 = nameof(LineDefinition.Description2); var desc3 = nameof(LineDefinition.Description3); var filterString = $"{titleS} contains '{search}' or {titleS2} contains '{search}' or {titleS3} contains '{search}' or {titleP} contains '{search}' or {titleP2} contains '{search}' or {titleP3} contains '{search}' or {code} contains '{search}' or {desc} contains '{search}' or {desc2} contains '{search}' or {desc3} contains '{search}'"; query = query.Filter(ExpressionFilter.Parse(filterString)); } return(Task.FromResult(query)); }
/// <summary> /// Returns an <see cref="List{TEntity}"/> based on a custom filtering function applied to the query, as well as /// optional select and expand arguments, checking the user READ permissions along the way /// </summary> /// <param name="filterFunc">Allows any kind of filtering on the query</param> /// <param name="expand">Optional expand argument</param> /// <param name="select">Optional select argument</param> /// <param name="orderby">Optional select argument</param> /// <param name="permissionsFilter">Optional filter argument, if null is passed the q</param> /// <param name="cancellation">Optional select argument</param> protected async Task <List <TEntity> > GetEntitiesByCustomQuery( Func <Query <TEntity>, Query <TEntity> > filterFunc, ExpressionExpand expand, ExpressionSelect select, ExpressionOrderBy orderby, ExpressionFilter permissionsFilter, CancellationToken cancellation) { // Prepare a query of the result, and clone it var repo = GetRepository(); var query = repo.Query <TEntity>(); // Apply custom filter function query = filterFunc(query); // Apply read permissions permissionsFilter ??= await UserPermissionsFilter(Constants.Read, cancellation); query = query.Filter(permissionsFilter); // Expand, Select and Order the result as specified in the OData agruments var expandedQuery = query.Expand(expand).Select(select).OrderBy(orderby ?? ExpressionOrderBy.Parse("Id")); // Required // Load the result into memory var data = await expandedQuery.ToListAsync(cancellation); // this is potentially unordered, should that be a concern? // TODO: This is slow and unused // Apply the permission masks (setting restricted fields to null) and adjust the metadata accordingly // await ApplyReadPermissionsMask(data, query, permissions, GetDefaultMask(), cancellation); // Return return(data); }
public void TestParseFilters_MultipleFilters() { var expressionFilter1 = new ExpressionFilter <PagingQueryBindingModelTestClass>(x => x.A, ComparisonType.Equal, "S"); var longValue = 1L; var expressionFilter2 = new ExpressionFilter <PagingQueryBindingModelTestClass>(x => x.Id, ComparisonType.Equal, longValue); var json1 = JsonConvert.SerializeObject(expressionFilter1); var json2 = JsonConvert.SerializeObject(expressionFilter2); var model = new PagingQueryBindingModel <PagingQueryBindingModelTestClass>(); model.Filter = new List <string> { json1, json2 }; var parsedFilters = model.ParseFilters(model.Filter); Assert.AreEqual(2, parsedFilters.Count); var firstParsedFilter = parsedFilters.First(); Assert.AreEqual(expressionFilter1.Comparison, firstParsedFilter.Comparison); Assert.AreEqual(expressionFilter1.Value, firstParsedFilter.Value); Assert.AreEqual(expressionFilter1.Property, firstParsedFilter.Property); var lastParsedFilter = parsedFilters.Last(); Assert.AreEqual(expressionFilter2.Comparison, lastParsedFilter.Comparison); Assert.AreEqual(expressionFilter2.Value, lastParsedFilter.Value); Assert.AreEqual(expressionFilter2.Property, lastParsedFilter.Property); }
public void ShouldBlockInvalidTraceExpression() { var filter = new ExpressionFilter("Id < 2"); var shouldTrace = filter.ShouldTrace(null, "Source", TraceEventType.Information, 3, "Message", null, null, null); Assert.IsFalse(shouldTrace); }
public void ShouldAllowValidTraceExpression() { var filter = new ExpressionFilter("Id < 2"); var shouldTrace = filter.ShouldTrace(null, "Source", TraceEventType.Information, 1, "Message", null, null, null); Assert.IsTrue(shouldTrace); }
public void TestCreateGetParticipantPersonSevisCommStatusesByParticipantIdQuery_Filtered() { var userAccount = new UserAccount { PrincipalId = 100, DisplayName = "display name", EmailAddress = "email" }; var participant = new Participant { ParticipantId = 1, ProjectId = 100 }; var participantPerson = new ParticipantPerson { ParticipantId = participant.ParticipantId, Participant = participant }; participant.ParticipantPerson = participantPerson; var sevisCommStatus = new SevisCommStatus { SevisCommStatusId = 500, SevisCommStatusName = "sevis comm status name" }; var status = new ParticipantPersonSevisCommStatus { Id = 1, AddedOn = DateTimeOffset.UtcNow, BatchId = "batchId", ParticipantId = participant.ParticipantId, ParticipantPerson = participantPerson, PrincipalId = userAccount.PrincipalId, SevisCommStatus = sevisCommStatus, SevisCommStatusId = sevisCommStatus.SevisCommStatusId, SevisOrgId = "sevis org Id", SevisUsername = "******" }; context.UserAccounts.Add(userAccount); context.Participants.Add(participant); context.ParticipantPersons.Add(participantPerson); context.SevisCommStatuses.Add(sevisCommStatus); context.ParticipantPersonSevisCommStatuses.Add(status); var defaultSorter = new ExpressionSorter <ParticipantPersonSevisCommStatusDTO>(x => x.AddedOn, SortDirection.Descending); var filter = new ExpressionFilter <ParticipantPersonSevisCommStatusDTO>(x => x.BatchId, ComparisonType.Equal, status.BatchId); var queryOperator = new QueryableOperator <ParticipantPersonSevisCommStatusDTO>(0, 1, defaultSorter); queryOperator.Filters.Add(filter); var results = ParticipantPersonsSevisQueries.CreateGetParticipantPersonSevisCommStatusesByParticipantIdQuery(context, participant.ProjectId, participant.ParticipantId, queryOperator); Assert.AreEqual(1, results.Count()); var firstResult = results.First(); Assert.AreEqual(status.Id, firstResult.Id); }
public void ParameterLessConstructor() { // arrange/act ExpressionFilter expressionFilter = new ExpressionFilter(); // assert Assert.Null(expressionFilter.Field); Assert.Null(expressionFilter.Operator); Assert.Null(expressionFilter.Value); }
public void TestConstructor_IntProperty() { var value = 1; var comparisonType = ComparisonType.Equal; var filter = new ExpressionFilter <ExpressionFilterTestClass>(x => x.Id, comparisonType, value); Assert.AreEqual(value, filter.Value); Assert.AreEqual("Id", filter.Property); Assert.AreEqual(comparisonType.Value, filter.Comparison); }
private async Task EnsureScheduledBuildFailSubscriptionExists(BuildDefinition pipeline, WebApiTeam team, bool persistChanges) { const string BuildFailureNotificationTag = "#AutomaticBuildFailureNotification"; var subscriptions = await service.GetSubscriptionsAsync(team.Id); var hasSubscription = subscriptions.Any(sub => sub.Description.Contains(BuildFailureNotificationTag)); logger.LogInformation("Team Is Subscribed TeamId = {0} PipelineId = {1} HasSubscription = {2}", team.Id, pipeline.Id, hasSubscription); if (!hasSubscription) { var filterModel = new ExpressionFilterModel { Clauses = new ExpressionFilterClause[] { new ExpressionFilterClause { Index = 1, LogicalOperator = "", FieldName = "Status", Operator = "=", Value = "Failed" }, new ExpressionFilterClause { Index = 2, LogicalOperator = "And", FieldName = "Definition name", Operator = "=", Value = $"\\{pipeline.Project.Name}\\{pipeline.Name}" }, new ExpressionFilterClause { Index = 3, LogicalOperator = "And", FieldName = "Build reason", Operator = "=", Value = "Scheduled" } } }; var filter = new ExpressionFilter("ms.vss-build.build-completed-event", filterModel); var identity = new IdentityRef { Id = team.Id.ToString(), Url = team.IdentityUrl }; var newSubscription = new NotificationSubscriptionCreateParameters { Channel = new UserSubscriptionChannel { UseCustomAddress = false }, Description = $"A build fails {BuildFailureNotificationTag}", Filter = filter, Scope = new SubscriptionScope { Type = "none", Id = pipeline.Project.Id }, Subscriber = identity, }; logger.LogInformation("Creating Subscription PipelineId = {0}, TeamId = {1}", pipeline.Id, team.Id); if (persistChanges) { var subscription = await service.CreateSubscriptionAsync(newSubscription); } } }
/// <summary> /// Returns a list of dynamic rows and optionally their count as per the specifications in <paramref name="args"/>. /// </summary> public virtual async Task <FactResult> GetFact(FactArguments args, CancellationToken cancellation) { await Initialize(cancellation); // Parse the parameters var filter = ExpressionFilter.Parse(args.Filter); var orderby = ExpressionOrderBy.Parse(args.OrderBy); var select = ExpressionFactSelect.Parse(args.Select); // Prepare the query var query = QueryFactory().FactQuery <TEntity>(); // Apply read permissions var permissionsFilter = await UserPermissionsFilter(PermissionActions.Read, cancellation); query = query.Filter(permissionsFilter); // Apply filter query = query.Filter(filter); // Apply orderby orderby ??= await DefaultOrderBy(cancellation); query = query.OrderBy(orderby); // Apply the paging (Protect against DOS attacks by enforcing a maximum page size) var top = args.Top; var skip = args.Skip; top = Math.Min(top, MaximumPageSize()); query = query.Skip(skip).Top(top); // Apply the select query = query.Select(select); // Load the data and count in memory List <DynamicRow> data; int?count = null; if (args.CountEntities) { (data, count) = await query.ToListAndCountAsync(MaximumCount, QueryContext, cancellation); } else { data = await query.ToListAsync(QueryContext, cancellation); } // Return return(new FactResult(data, count)); }
/// <summary> /// Returns an aggregated list of dynamic rows and any tree dimension ancestors as per the specifications in <paramref name="args"/>. /// </summary> public virtual async Task <AggregateResult> GetAggregate(GetAggregateArguments args, CancellationToken cancellation) { await Initialize(cancellation); // Parse the parameters var filter = ExpressionFilter.Parse(args.Filter); var having = ExpressionHaving.Parse(args.Having); var select = ExpressionAggregateSelect.Parse(args.Select); var orderby = ExpressionAggregateOrderBy.Parse(args.OrderBy); // Prepare the query var query = QueryFactory().AggregateQuery <TEntity>(); // Retrieve and Apply read permissions var permissionsFilter = await UserPermissionsFilter(PermissionActions.Read, cancellation); query = query.Filter(permissionsFilter); // Important // Filter and Having query = query.Filter(filter); query = query.Having(having); // Apply the top parameter var top = args.Top == 0 ? int.MaxValue : args.Top; // 0 means get all top = Math.Min(top, MaximumAggregateResultSize + 1); query = query.Top(top); // Apply the select, which has the general format 'Select=A+B.C,Sum(D)' query = query.Select(select); // Apply the orderby, which has the general format 'A+B.C desc,Sum(D) asc' query = query.OrderBy(orderby); // Load the data in memory var output = await query.ToListAsync(QueryContext, cancellation); var data = output.Rows; var ancestors = output.Ancestors.Select(e => new DimensionAncestorsResult(e.Result, e.IdIndex, e.MinIndex)); // Put a limit on the number of data points returned, to prevent DoS attacks if (data.Count > MaximumAggregateResultSize) { var msg = _localizer["Error_NumberOfDataPointsExceedsMaximum0", MaximumAggregateResultSize]; throw new ServiceException(msg); } // Return return(new AggregateResult(data, ancestors)); }
public void FieldOperatorValueProvided() { // arrange/act ExpressionFilter expressionFilter = new ExpressionFilter() { Field = "supdocname", Operator = "=", Value = "foo bar" }; // assert Assert.Equal("supdocname", expressionFilter.Field); Assert.Equal("=", expressionFilter.Operator); Assert.Equal("foo bar", expressionFilter.Value); }
///// <summary> ///// If the user is subject to field-level access control, this method hides all the fields ///// that the user has no access to and modifies the metadata of the Entities accordingly ///// </summary> //protected override async Task ApplyReadPermissionsMask( // List<TEntity> resultEntities, // Query<TEntity> query, // IEnumerable<AbstractPermission> permissions, // MaskTree defaultMask, // CancellationToken cancellation) //{ // bool defaultMaskIsUnrestricted = defaultMask == null || defaultMask.IsUnrestricted; // bool allPermissionMasksAreEmpty = permissions.All(e => string.IsNullOrWhiteSpace(e.Mask)); // bool anEmptyCriteriaIsPairedWithEmptyMask = // permissions.Any(e => string.IsNullOrWhiteSpace(e.Mask) && string.IsNullOrWhiteSpace(e.Criteria)); // if ((allPermissionMasksAreEmpty || anEmptyCriteriaIsPairedWithEmptyMask) && defaultMaskIsUnrestricted) // { // // Optimization: if all masks are unrestricted, or an empty criteria is paired with an empty mask then we can skip this whole ordeal // return; // } // else // { // // Maps every Entity to its list of masks // var maskedEntities = new Dictionary<Entity, HashSet<string>>(); // var unrestrictedEntities = new HashSet<Entity>(); // // Marks the Entity and all Entities reachable from it as unrestricted // void MarkUnrestricted(Entity entity, Type entityType) // { // if (entity == null) // { // return; // } // if (maskedEntities.ContainsKey(entity)) // { // maskedEntities.Remove(entity); // } // if (!unrestrictedEntities.Contains(entity)) // { // unrestrictedEntities.Add(entity); // foreach (var key in entity.EntityMetadata.Keys) // { // var prop = entityType.GetProperty(key); // if (prop.PropertyType.IsList()) // { // // This is a navigation collection, iterate over the rows // var collection = prop.GetValue(entity); // if (collection != null) // { // var collectionType = prop.PropertyType.CollectionType(); // foreach (var row in collection.Enumerate<Entity>()) // { // MarkUnrestricted(row, collectionType); // } // } // } // else // { // // This is a normal navigation property // var propValue = prop.GetValue(entity) as Entity; // var propType = prop.PropertyType; // MarkUnrestricted(propValue, propType); // } // } // } // } // // Goes over this entity and every entity reachable from it and marks each one with the accessible fields // void MarkMask(Entity entity, Type entityType, MaskTree mask) // { // if (entity == null) // { // return; // } // if (mask.IsUnrestricted) // { // MarkUnrestricted(entity, entityType); // } // else // { // if (unrestrictedEntities.Contains(entity)) // { // // Nothing to mask in an unrestricted Entity // return; // } // else // { // if (!maskedEntities.ContainsKey(entity)) // { // // All entities will have their basic fields accessible // var accessibleFields = new HashSet<string>(); // foreach (var basicField in entityType.AlwaysAccessibleFields()) // { // accessibleFields.Add(basicField.Name); // } // maskedEntities[entity] = accessibleFields; // } // { // var accessibleFields = maskedEntities[entity]; // foreach (var requestedField in entity.EntityMetadata.Keys) // { // var prop = entityType.GetProperty(requestedField); // if (mask.ContainsKey(requestedField)) // { // // If the field is included in the mask, make it accessible // if (!accessibleFields.Contains(requestedField)) // { // accessibleFields.Add(requestedField); // } // if (prop.PropertyType.IsList()) // { // // This is a navigation collection, iterate over the rows and apply the mask subtree // var collection = prop.GetValue(entity); // if (collection != null) // { // var collectionType = prop.PropertyType.CollectionType(); // foreach (var row in collection.Enumerate<Entity>()) // { // MarkMask(row, collectionType, mask[requestedField]); // } // } // } // else // { // var foreignKeyNameAtt = prop.GetCustomAttribute<ForeignKeyAttribute>(); // if (foreignKeyNameAtt != null) // { // // Make sure if the navigation property is included that its foreign key is included as well // var foreignKeyName = foreignKeyNameAtt.Name; // if (!string.IsNullOrWhiteSpace(foreignKeyName) && !accessibleFields.Contains(foreignKeyName)) // { // accessibleFields.Add(foreignKeyName); // } // // Use recursion to update the rest of the tree // var propValue = prop.GetValue(entity) as Entity; // var propType = prop.PropertyType; // MarkMask(propValue, propType, mask[requestedField]); // } // } // } // } // } // } // } // } // if (permissions.All(e => string.IsNullOrWhiteSpace(e.Criteria))) // { // // Having no criteria is a very common case that can be optimized by skipping the database call // var addDefault = permissions.Any(p => string.IsNullOrWhiteSpace(p.Mask)); // var masks = permissions.Select(e => e.Mask).Where(e => !string.IsNullOrWhiteSpace(e)); // var maskTrees = masks.Select(mask => MaskTree.Parse(mask)).ToList(); // if (addDefault) // { // maskTrees.Add(defaultMask); // } // // Calculate the union of all the mask fields // var maskUnion = maskTrees.Aggregate(MaskTree.BasicFieldsMaskTree(), (t1, t2) => t1.UnionWith(t2)); // // Mark all the entities // var entityType = typeof(TEntity); // foreach (var item in resultEntities) // { // MarkMask(item, entityType, maskUnion); // } // } // else // { // // an array of every criteria and every mask // var maskAndCriteriaArray = permissions // .Where(e => !string.IsNullOrWhiteSpace(e.Criteria)) // Optimization: a null criteria is satisfied by the entire list of entities // .GroupBy(e => e.Criteria) // .Select(g => new // { // Criteria = g.Key, // Mask = g.Select(e => string.IsNullOrWhiteSpace(e.Mask) ? defaultMask : MaskTree.Parse(e.Mask)) // .Aggregate((t1, t2) => t1.UnionWith(t2)) // takes the union of all the mask trees // }).ToArray(); // // This mask applies to every single entity since the criteria is null // var universalMask = permissions // .Where(e => string.IsNullOrWhiteSpace(e.Criteria)) // .Distinct() // .Select(e => string.IsNullOrWhiteSpace(e.Mask) ? defaultMask : MaskTree.Parse(e.Mask)) // .Aggregate(MaskTree.BasicFieldsMaskTree(), (t1, t2) => t1.UnionWith(t2)); // we use a seed here since if the collection is empty this will throw an error // var criteriaWithIndexes = maskAndCriteriaArray // .Select((e, index) => new IndexAndCriteria { Criteria = e.Criteria, Index = index }); // var criteriaMapList = await query.GetIndexToIdMap<TKey>(criteriaWithIndexes, cancellation); // // Go over the Ids in the result and apply all relevant masks to said entity // var entityType = typeof(TEntity); // var criteriaMapDictionary = criteriaMapList // .GroupBy(e => e.Id) // .ToDictionary(e => e.Key, e => e.ToList()); // foreach (var entity in resultEntities) // { // var id = entity.Id; // MaskTree mask; // if (criteriaMapDictionary.ContainsKey(id)) // { // // Those are entities that satisfy one or more non-null Criteria // mask = criteriaMapDictionary[id] // .Select(e => maskAndCriteriaArray[e.Index].Mask) // .Aggregate((t1, t2) => t1.UnionWith(t2)) // .UnionWith(universalMask); // } // else // { // // Those are entities that belong to the universal mask of null criteria // mask = universalMask; // } // MarkMask(entity, entityType, mask); // } // } // // This where field-level security is applied, we read all masked entities and apply the // // masks on them by setting the field to null and adjusting the metadata accordingly // foreach (var pair in maskedEntities) // { // var entity = pair.Key; // var accessibleFields = pair.Value; // List<Action> updates = new List<Action>(entity.EntityMetadata.Keys.Count); // foreach (var requestedField in entity.EntityMetadata.Keys) // { // if (!accessibleFields.Contains(requestedField)) // { // // Mark the field as restricted (we delay the call to avoid the dreadful "collection-was-modified" Exception) // updates.Add(() => entity.EntityMetadata[requestedField] = FieldMetadata.Restricted); // // Set the field to null // var prop = entity.GetType().GetProperty(requestedField); // try // { // prop.SetValue(entity, null); // } // catch (Exception ex) // { // if (prop.PropertyType.IsValueType && Nullable.GetUnderlyingType(prop.PropertyType) == null) // { // // Programmer mistake // throw new InvalidOperationException($"Entity field {prop.Name} has a non nullable type, all Entity fields must have a nullable type"); // } // else // { // throw ex; // } // } // } // } // updates.ForEach(a => a()); // } // } //} /// <summary> /// Many actions are simply a list of Ids /// This is a utility method for permission actions that do not have a mask, it checks that the /// user has enough permissions to cover the entire list of Ids affected by the action, if not it /// throws a <see cref="ForbiddenException"/>, unless the uncovered Ids are invisible to the user /// or do not exist, in that case it returns a watered down list of Ids the user can perform the /// action on. Handling missing and invisible Ids are up to the API implementation /// </summary> protected virtual async Task <List <TKey> > CheckActionPermissionsBefore(ExpressionFilter actionFilter, List <TKey> ids) { if (actionFilter == null) { return(ids); // No row level security } else { var actionedIds = ids.Distinct(); var baseQuery = GetRepository() .Query <TEntity>() .Select("Id") .FilterByIds(actionedIds); // First query to count how many Ids the user can action var actionableEntities = await baseQuery .Filter(actionFilter) .ToListAsync(cancellation: default); if (actionableEntities.Count == actionedIds.Count()) { return(ids); // The user has permission to view and perform the action on all the Ids } else // Else Potential problem, either the user (1) can't view one or more of the Ids (2) or can't perform the action on said Ids { // Do a second query to verify that the missing Ids are solely due to read permission (not action permissions) var readFilter = await UserPermissionsFilter(Constants.Read, cancellation : default); var readableIdsCount = await baseQuery .Filter(readFilter) .CountAsync(cancellation: default); if (actionableEntities.Count < readableIdsCount) // Definitely a problem { // Trying to perform an action on Ids you can see but cannot perform that action onto throw new ForbiddenException(); } else { // Trying to perform an action on Ids that are invisible to you, treat them like you would treat entirely missing Ids // Return the actionable Ids while preserving their order var actionableIdsHash = actionableEntities.Select(e => e.Id).ToHashSet(); return(ids.Where(id => actionableIdsHash.Contains(id)).ToList()); } } } }
public void GetExpressionByFilter() { const string content = "testing converstation"; Member member = Instance.LoggedInMember; Expression expression = CreateExpression(member, content); ExpressionFilter filter = new ExpressionFilter(); filter.Author.Add(member); filter.AuthoredAt.AddTerm(FilterTerm.Operator.Equal, expression.AuthoredAt); IList<Expression> expressions = new List<Expression>(Instance.Get.Expressions(filter)); Assert.AreEqual(1, expressions.Count); Expression newConv = expressions[0]; Assert.AreEqual(expression.ID, newConv.ID); Assert.AreEqual(expression.Content, newConv.Content); Assert.AreEqual(member, newConv.Author); }
protected override Query <IdentityServerUser> Search(Query <IdentityServerUser> query, GetArguments args) { string search = args.Search; if (!string.IsNullOrWhiteSpace(search)) { search = search.Replace("'", "''"); // escape quotes by repeating them var email = nameof(IdentityServerUser.Email); var filterString = $"{email} contains '{search}'"; query = query.Filter(ExpressionFilter.Parse(filterString)); } return(query); }
public void TestToString_HasFiltersAndSorters() { var filter1 = new ExpressionFilter <QueryableOperatorTestClass>(x => x.Id, ComparisonType.Equal, 1); var filter2 = new ExpressionFilter <QueryableOperatorTestClass>(x => x.Id, ComparisonType.LessThan, 2); var sorter1 = new ExpressionSorter <QueryableOperatorTestClass>(x => x.Id, SortDirection.Descending); var sorter2 = new ExpressionSorter <QueryableOperatorTestClass>(x => x.Id, SortDirection.Ascending); var defaultSorter = new ExpressionSorter <QueryableOperatorTestClass>(x => x.Id, SortDirection.Ascending); var queryOperator = new QueryableOperator <QueryableOperatorTestClass>(0, 10, defaultSorter, new List <IFilter> { filter1, filter2 }, new List <ISorter> { sorter1, sorter2 }); Assert.IsNotNull(queryOperator.ToString()); }
public static Expression GetExpression <T>(ParameterExpression param, ExpressionFilter filter) { MemberExpression member = Expression.Property(param, filter.PropertyName); ConstantExpression constant = Expression.Constant(filter.Value); if (filter.TypeO == "text") { constant = Expression.Constant(Convert.ToString(filter.Value), typeof(string)); } if (filter.TypeO == "int") { constant = Expression.Constant(Convert.ToInt32(filter.Value), typeof(int)); } switch (filter.Comparison) { case Comparison.Equal: return(Expression.Equal(member, constant)); case Comparison.GreaterThan: return(Expression.GreaterThan(member, constant)); case Comparison.GreaterThanOrEqual: return(Expression.GreaterThanOrEqual(member, constant)); case Comparison.LessThan: return(Expression.LessThan(member, constant)); case Comparison.LessThanOrEqual: return(Expression.LessThanOrEqual(member, constant)); case Comparison.NotEqual: return(Expression.NotEqual(member, constant)); case Comparison.Contains: return(Expression.Call(member, containsMethod, constant)); case Comparison.StartsWith: return(Expression.Call(member, startsWithMethod, constant)); case Comparison.EndsWith: return(Expression.Call(member, endsWithMethod, constant)); default: return(null); } }
/// <summary> /// Returns a list of entities as per the specifications in the <see cref="GetChildrenArguments{TKey}"/> /// </summary> public virtual async Task <(List <TEntity>, Extras)> GetChildrenOf(GetChildrenArguments <TKey> args, CancellationToken cancellation) { // Parse the parameters var expand = ExpressionExpand.Parse(args.Expand); var select = ParseSelect(args.Select); var filter = ExpressionFilter.Parse(args.Filter); var orderby = ExpressionOrderBy.Parse("Node"); var ids = args.I ?? new List <TKey>(); // Load the data var data = await GetEntitiesByCustomQuery(q => q.FilterByParentIds(ids, args.Roots).Filter(filter), expand, select, orderby, null, cancellation); var extras = await GetExtras(data, cancellation); // Transform and Return return(data, extras); }
protected override Query <Agent> Search(Query <Agent> query, GetArguments args) { string search = args.Search; if (!string.IsNullOrWhiteSpace(search)) { search = search.Replace("'", "''"); // escape quotes by repeating them var name = nameof(Agent.Name); var name2 = nameof(Agent.Name2); var name3 = nameof(Agent.Name3); var filterString = $"{name} contains '{search}' or {name2} contains '{search}' or {name3} contains '{search}'"; query = query.Filter(ExpressionFilter.Parse(filterString)); } return(query); }
protected override Task <EntityQuery <AccountType> > Search(EntityQuery <AccountType> query, GetArguments args, CancellationToken _) { string search = args.Search; if (!string.IsNullOrWhiteSpace(search)) { search = search.Replace("'", "''"); // escape quotes by repeating them var name = nameof(AccountType.Name); var name2 = nameof(AccountType.Name2); var name3 = nameof(AccountType.Name3); var filterString = $"{name} contains '{search}' or {name2} contains '{search}' or {name3} contains '{search}'"; query = query.Filter(ExpressionFilter.Parse(filterString)); } return(Task.FromResult(query)); }
protected override Task <EntityQuery <DetailsEntry> > Search(EntityQuery <DetailsEntry> query, GetArguments args, CancellationToken _) { string search = args.Search; if (!string.IsNullOrWhiteSpace(search)) { search = search.Replace("'", "''"); // escape quotes by repeating them var line = nameof(DetailsEntry.Line); var memo = nameof(LineForQuery.Memo); var text1 = nameof(LineForQuery.Text1); var filterString = $"{line}.{memo} contains '{search}' or {line}.{text1} contains '{search}' "; query = query.Filter(ExpressionFilter.Parse(filterString)); } return(Task.FromResult(query)); }
protected override Task <EntityQuery <DashboardDefinition> > Search(EntityQuery <DashboardDefinition> query, GetArguments args, CancellationToken _) { string search = args.Search; if (!string.IsNullOrWhiteSpace(search)) { search = search.Replace("'", "''"); // escape quotes by repeating them var title = nameof(DashboardDefinition.Title); var title2 = nameof(DashboardDefinition.Title2); var title3 = nameof(DashboardDefinition.Title3); var code = nameof(DashboardDefinition.Code); var filterString = $"{title} contains '{search}' or {title2} contains '{search}' or {title3} contains '{search}' or {code} contains '{search}'"; query = query.Filter(ExpressionFilter.Parse(filterString)); } return(Task.FromResult(query)); }
public void GetExpressionByReplyReferences() { Expression @base = CreateExpression(Instance.LoggedInMember, "base"); Expression reply = Instance.Create.Expression(Instance.LoggedInMember, "a reply", null, @base); Expression unrelated = CreateExpression(Instance.LoggedInMember, "something else"); ExpressionFilter filter = new ExpressionFilter(); filter.InReplyTo.Add(@base); ICollection<Expression> expressions = Instance.Get.Expressions(filter); Assert.AreEqual(1, expressions.Count); CollectionAssert.Contains(expressions, reply); CollectionAssert.DoesNotContain(expressions, unrelated); filter = new ExpressionFilter(); filter.Replies.Add(reply); expressions = Instance.Get.Expressions(filter); Assert.AreEqual(1, expressions.Count); CollectionAssert.Contains(expressions, @base); CollectionAssert.DoesNotContain(expressions, unrelated); }
public void GetExpressionByMentions() { Member firstMember = EntityFactory.CreateMember("test1"); Expression firstExpression = CreateExpression(Instance.LoggedInMember, "testing - #1"); firstExpression.Mentions.Add(firstMember); firstExpression.Save(); Member secondMember = EntityFactory.CreateMember("test2"); Expression secondExpression = CreateExpression(Instance.LoggedInMember, "testing - #2"); secondExpression.Mentions.Add(secondMember); secondExpression.Save(); ExpressionFilter filter = new ExpressionFilter(); filter.Mentions.Add(firstMember); ICollection<Expression> expressions = Instance.Get.Expressions(filter); Assert.AreEqual(1, expressions.Count); CollectionAssert.Contains(expressions, firstExpression); CollectionAssert.DoesNotContain(expressions, secondExpression); filter = new ExpressionFilter(); filter.Mentions.Add(secondMember); expressions = Instance.Get.Expressions(filter); Assert.AreEqual(1, expressions.Count); CollectionAssert.Contains(expressions, secondExpression); CollectionAssert.DoesNotContain(expressions, firstExpression); filter = new ExpressionFilter(); filter.Mentions.Add(firstMember); filter.Mentions.Add(secondMember); expressions = Instance.Get.Expressions(filter); Assert.AreEqual(2, expressions.Count); CollectionAssert.Contains(expressions, secondExpression); CollectionAssert.Contains(expressions, firstExpression); }
public void GetExpressionByBaseAssets() { Story story = EntityFactory.CreateStory("fly to the Moon using a magnet and will power", SandboxProject); Expression firstExpression = CreateExpression(Instance.LoggedInMember, "testing - #1"); firstExpression.Mentions.Add(story); firstExpression.Save(); Test test = EntityFactory.CreateTest("check the direction", story); Expression secondExpression = CreateExpression(Instance.LoggedInMember, "testing - #2"); secondExpression.Mentions.Add(test); secondExpression.Save(); ExpressionFilter filter = new ExpressionFilter(); filter.Mentions.Add(story); ICollection<Expression> expressions = Instance.Get.Expressions(filter); Assert.AreEqual(1, expressions.Count); CollectionAssert.Contains(expressions, firstExpression); CollectionAssert.DoesNotContain(expressions, secondExpression); filter = new ExpressionFilter(); filter.Mentions.Add(test); expressions = Instance.Get.Expressions(filter); Assert.AreEqual(1, expressions.Count); CollectionAssert.Contains(expressions, secondExpression); CollectionAssert.DoesNotContain(expressions, firstExpression); filter = new ExpressionFilter(); filter.Mentions.Add(story); filter.Mentions.Add(test); expressions = Instance.Get.Expressions(filter); Assert.AreEqual(2, expressions.Count); CollectionAssert.Contains(expressions, firstExpression); CollectionAssert.Contains(expressions, secondExpression); }