/// <summary> /// Returns the entities as per the specifications in the get request /// </summary> protected virtual async Task <GetAggregateResponse> GetAggregateImplAsync(GetAggregateArguments args) { // Parse the parameters var filter = FilterExpression.Parse(args.Filter); var select = AggregateSelectExpression.Parse(args.Select); // Prepare the query var query = GetRepository().AggregateQuery <TEntity>(); // Retrieve the user permissions for the current view var permissions = await UserPermissions(Constants.Read); var permissionsCount = permissions.Count(); // Filter out permissions with masks that would be violated by the filter argument // orderby on the other hand is always mandated to be a subset of the selected parameters // and those in turn must be universally visible to the user, so no need to check orderby var defaultMask = GetDefaultMask() ?? new MaskTree(); permissions = FilterViolatedPermissionsForAggregateQuery(permissions, defaultMask, filter, select); var filteredPermissionCount = permissions.Count(); var isPartial = permissionsCount != filteredPermissionCount; // Apply read permissions FilterExpression permissionsCriteria = GetReadPermissionsCriteria(permissions); query = query.Filter(permissionsCriteria); // Filter query = query.Filter(filter); // Apply the top parameter var top = args.Top == 0 ? int.MaxValue : args.Top; // 0 means get all top = Math.Min(top, MAXIMUM_AGGREGATE_RESULT_SIZE + 1); query = query.Top(top); // Apply the select, which has the general format 'Select=A,B/C,D' query = query.Select(select); // Load the data in memory var result = await query.ToListAsync(); // Put a limit on the number of data points returned, to prevent DoS attacks if (result.Count > MAXIMUM_AGGREGATE_RESULT_SIZE) { var msg = _localizer["Error_NumberOfDataPointsExceedsMaximum0", MAXIMUM_AGGREGATE_RESULT_SIZE]; throw new BadRequestException(msg); } // Finally return the result return(new GetAggregateResponse { Top = args.Top, IsPartial = isPartial, Result = result, RelatedEntities = new Dictionary <string, IEnumerable <Entity> >() // TODO: Add ancestors of tree dimensions }); }
private MaskTree UpdateUserMaskAsPerAggregateSelect(AggregateSelectExpression select, MaskTree userMask) { if (select != null) { var aggSelectPaths = select.Select(e => (e.Path, e.Property)); var aggSelectMask = MaskTree.GetMaskTree(aggSelectPaths); var aggSelectAccess = Normalize(aggSelectMask); userMask = userMask.UnionWith(aggSelectAccess); } return(userMask); }
/// <summary> /// Removes from the permissions all permissions that would be violated by the filter or aggregate select, the behavior /// of the system here is that when a user orders by a field that she has no full access too, she only sees the /// rows where she can see that field, sometimes resulting in a shorter list, this is to prevent the user gaining /// any insight over fields she has no access to by filter or order the data /// </summary> protected IEnumerable <AbstractPermission> FilterViolatedPermissionsForAggregateQuery(IEnumerable <AbstractPermission> permissions, MaskTree defaultMask, FilterExpression filter, AggregateSelectExpression select) { // Step 1 - Build the "User Mask", i.e the mask containing the fields mentioned in the relevant components of the user query var userMask = MaskTree.BasicFieldsMaskTree(); userMask = UpdateUserMaskAsPerFilter(filter, userMask); userMask = UpdateUserMaskAsPerAggregateSelect(select, userMask); // Filter out those permissions whose mask does not cover the entire user mask return(FilterViolatedPermissionsInner(permissions, defaultMask, userMask)); }