/// <summary> /// Build and return the console navigation tree. /// </summary> /// <returns> /// A <see cref="EntityData"/> containing the tree structure. /// </returns> /// <exception cref="InvalidOperationException"> /// The <see cref="ReadiNow.IO.RequestContext"/> is not set. /// </exception> public EntityData GetTree() { if (!ReadiNow.IO.RequestContext.IsSet) { throw new InvalidOperationException("Request context not set"); } EntityData entityData; const string hintText = "navItems"; using (new SecurityBypassContext()) using (Profiler.Measure("ConsoleTreeRepository.GetTree")) { // Construct and run the query EntityRequest entityRequest = new EntityRequest { Entities = new EntityRef(WellKnownAliases.CurrentTenant.TopMenu).ToEnumerable( ), RequestString = ConsoleTreeRequest, Hint = hintText, IgnoreResultCache = true // this data is cached by the CachingConsoleTreeRepository }; entityData = BulkRequestRunner.GetEntities(entityRequest).FirstOrDefault(); // It's not completely clear why we are using SecurityBypassContext and manually checking, rather than relying on the security model. // One possible explanation is that the 'securesTo' flags cause default reports and picker reports to be readable, which may be undesirable in the UI // If the user is not an administrator, remove entries without the "core:allowDisplay" relationship // to either their own user account or a role they are in. Prune(entityData, ReadiNow.IO.RequestContext.GetContext().Identity.Id); return(entityData); } }
IEnumerable <EntityData> IEntityInfoRead.GetEntitiesByType(EntityRef entityType, bool includeDerivedTypes, EntityMemberRequest request) { if (string.IsNullOrEmpty(request.RequestString)) { Assert.Ignore("GetEntitiesByType not supported by BulkRequestRunner without request string"); } else { var entityRequest = new EntityRequest { QueryType = includeDerivedTypes ? QueryType.Instances : QueryType.ExactInstances, IgnoreResultCache = true, Entities = entityType.ToEnumerable(), RequestString = request.RequestString }; // preferred path .. but still need to get away from passing EntityMemberRequest IEnumerable <EntityData> res = BulkRequestRunner.GetEntitiesByType(entityRequest); return(res); } return(null); }
internal void RemoveUnreferencedWorkflowTraces() { var toDelete = BulkRequestRunner.FetchFilteredEntities("runTraceLogEntry", "[Workflow run] is null"); if (toDelete.Count() > 0) { EventLog.Application.WriteInformation("Deleting {0} old workflow runs trace log entries", toDelete.Count()); Entity.Delete(toDelete.Select(r => r.Id)); } }
public void Test_Can_Read(string field, string instance) { string request = field; EntityData res = BulkRequestRunner.GetEntityData(new EntityRef(instance), request); Assert.That(res, Is.Not.Null, "res"); Assert.That(res.Fields, Has.Count.EqualTo(1)); Assert.That(res.Fields[0].FieldId.Alias, Is.EqualTo(field)); Assert.That(res.Fields[0].Value.Value, Is.EqualTo(true)); }
public void Test_CalculatedFieldsInBulkRequestRunner_FieldTypes(string fieldTypeAlias, string calc, string exected, Type databaseType, Type dataType) { var scenario = CreateScenario(fieldTypeAlias, calc); var field = scenario.Item1; var inst = scenario.Item2; string request = "#" + field.Id.ToString(); EntityData result = BulkRequestRunner.GetEntityData(new EntityRef(inst.Id), request); // Check results CheckResult(result, exected, databaseType, dataType); }
public IEnumerable <EntityData> GetEntitiesData(IEnumerable <EntityRef> entities, EntityMemberRequest request, SecurityOption securityOption = SecurityOption.SkipDenied) { EntityRequest entityRequest = new EntityRequest { QueryType = QueryType.Basic, IgnoreResultCache = true, Entities = entities, Request = request }; return(BulkRequestRunner.GetEntitiesData(entityRequest)); }
/// <summary> /// Fetch a form as Entity data /// </summary> /// <param name="formId">The form identifier.</param> /// <param name="isInDesignMode">if set to <c>true</c> [is in design mode].</param> /// <returns></returns> /// <exception cref="FormNotFoundException"> /// </exception> public static EntityData GetFormAsEntityData(long formId, bool isInDesignMode = false) { string request = CustomEditFormHelper.GetHtmlFormQuery(isInDesignMode); // Check security and existance try { IEntity form = Entity.Get(formId); if (form == null) { EventLog.Application.WriteError("Attempted to fetch an unknown form/screen. {0}", formId); throw new FormNotFoundException(); } } catch (ArgumentException) // from EntityIdentificationCache.GetId { EventLog.Application.WriteError("Attempted to fetch an unknown form/screen. {0}", formId); throw new FormNotFoundException(); } // Load form data EntityData formEntityData; using (Profiler.Measure("Get form data")) using (new SecurityBypassContext()) { formEntityData = BulkRequestRunner.GetEntityData(formId, request, "GetFormAsEntityData"); } if (formEntityData == null) { EventLog.Application.WriteError("Attempted to fetch an unknown form. {0}", formId); throw new FormNotFoundException(); } // // If we are not in design mode we need to go an fetch any missing default reports. // We don't want to fetch these for design mode because that will 'fix' them in the form. // if (!isInDesignMode) { using (Profiler.Measure("Get form default reports")) using (new SecurityBypassContext()) { SetDefaultReportsForRelationshipControls(formEntityData); } } return(formEntityData); }
public EntityData GetEntityData(EntityRef id, EntityMemberRequest request) { if (string.IsNullOrEmpty(request.RequestString)) { // obsolete path var results = GetEntitiesData(id.ToEnumerable(), request); EntityData res = results.FirstOrDefault(); return(res); } else { // preferred path .. but still need to get away from passing EntityMemberRequest EntityData res = BulkRequestRunner.GetEntityData(id, request.RequestString); return(res); } }
/// <summary> /// Pre-load data into the entity model cache according to the specified query. /// Note: subsequent calls to preload take almost no time, unless the data queried has changed. /// Caution: this mutates the entity request. /// </summary> /// <param name="request">Entity info request to preload.</param> public static void Preload(EntityRequest request) { using (Profiler.Measure(request.Hint ?? "Preload")) using (new SecurityBypassContext( )) { // Check cache and/or get unsecured result // Note: the results don't actually get secured at this point (we don't want to for the purpose of preload. So we're in a SecurityBypassContext) // The reason we convert to entity data is because it's smart enough to fill in the empty fields/relationships for which there is no data. // So we can cache the fact that those fields/relationships are empty. (BulkRequestResult alone doesn't give us this info). request.DontProcessResultIfFromCache = true; IEnumerable <EntityData> rootEntities; if (request.QueryType == QueryType.ExactInstances || request.QueryType == QueryType.Instances) { rootEntities = BulkRequestRunner.GetEntitiesByType(request); } else { rootEntities = BulkRequestRunner.GetEntitiesData(request); } if (!request.ResultFromCache) { long isOfTypeId = WellKnownAliases.CurrentTenant.IsOfType; // Flatten the graph IEnumerable <EntityData> allEntityData = Delegates.WalkGraph(rootEntities, entityData => entityData.Relationships.SelectMany(relType => relType.Entities)); using (Entity.DistributedMemoryManager.Suppress( )) { // Extract field/relationship data and shove into cache foreach (EntityData entityData in allEntityData) { AddEntityToCache(entityData, isOfTypeId); AddFieldsToCache(entityData); AddRelationshipsToCache(entityData); } } } } }
public void Test_Data_Changed() { var scenario = CreateScenario("core:stringField", "'Test'+Name"); var field = scenario.Item1; var inst = scenario.Item2; string request = "#" + field.Id.ToString(); EntityData result = BulkRequestRunner.GetEntityData(new EntityRef(inst.Id), request); CheckResult(result, "TestName1", typeof(StringType), typeof(string)); // Rename inst.Name = "NewName"; inst.Save(); result = BulkRequestRunner.GetEntityData(new EntityRef(inst.Id), request); CheckResult(result, "TestNewName", typeof(StringType), typeof(string)); }
public void GetEntitiesFilteredNonDerived() { if (_runner == "EntityInfoService") { Assert.Ignore(); } var request = new EntityRequest("core:report", "name", QueryType.FilterExactInstances, "Test"); request.Filter = "[Name] = 'Inboxes'"; IEnumerable <EntityData> results = BulkRequestRunner.GetEntities(request); Assert.That(results.Count(), Is.EqualTo(1)); EntityData resource = results.Single(); Assert.That(resource.Fields, Has.Count.EqualTo(1)); Assert.That(resource.Fields[0].Value.ValueString, Is.EqualTo("Inboxes")); }
/// <summary> /// Add a request for a set of entities and queries. /// </summary> /// <param name="batch">The batch.</param> /// <param name="hintText">A hint about what this query is doing. Use for logging/diagnostics only.</param> /// <exception cref="System.ArgumentException"> /// </exception> /// <exception cref="System.Exception">Internal error: batch query mismatch.</exception> public void AddEntityRequestBatch(JsonQueryBatchRequest batch, string hintText = null) { JsonQueryResult result = _result; #pragma warning disable 618 EntityInfoService svc = _svc; #pragma warning restore 618 var memberRequests = new EntityMemberRequest[batch.Queries.Length]; foreach (JsonQuerySingleRequest request in batch.Requests) { string requestHintText = request.Hint ?? hintText; _entitiesVisited.Clear( ); var singleResult = new JsonSingleQueryResult { Hint = request.Hint, Ids = new List <long>( ) }; try { EntityMemberRequest memberRequest; // Do not require access to the individual query components using (PerformanceCounters.Measure(EntityInfoServicePerformanceCounters.CategoryName, EntityInfoServicePerformanceCounters.RequestCountersPrefix)) using (new SecurityBypassContext( )) { // Get/parse the request query. (Either parse, or load from previous cache as part of this request). if (request.QueryIndex < 0 || request.QueryIndex >= batch.Queries.Length) { EventLog.Application.WriteError("Cannot locate query string."); singleResult.Code = HttpStatusCode.BadRequest; continue; } memberRequest = memberRequests[request.QueryIndex]; if (memberRequest == null) { string queryString = batch.Queries[request.QueryIndex]; //// Parse the request try { memberRequest = Factory.RequestParser.ParseRequestQuery(queryString); } catch (Exception ex) { EventLog.Application.WriteError(ex.ToString( )); singleResult.Code = HttpStatusCode.BadRequest; continue; } memberRequests[request.QueryIndex] = memberRequest; } } // Get the entityRefs IEnumerable <EntityRef> ids = Enumerable.Empty <EntityRef>( ); if (request.Aliases != null && request.Aliases.Length > 0) { ids = ids.Union(request.Aliases.Select(ConvertId)); } if (request.Ids != null && request.Ids.Length > 0) { ids = ids.Union(request.Ids.Select(ConvertId)); } IList <EntityRef> entityRefs = ids.ToList( ); if (entityRefs.All(er => er == null)) { singleResult.Code = HttpStatusCode.NotFound; continue; } // Run the request IEnumerable <EntityData> entitiesData; try { if (BulkRequestHelper.IsValidForBulkRequest(memberRequest)) { var entityRequest = new EntityRequest { QueryType = request.QueryType, RequestString = memberRequest.RequestString, Hint = requestHintText, Entities = entityRefs, Filter = request.Filter, Isolated = request.Isolated }; // Run the request entitiesData = BulkRequestRunner.GetEntities(entityRequest); singleResult.Cached = entityRequest.ResultFromCache; } else { EventLog.Application.WriteWarning( "Request cannot be handled by BulkRequest. Hint: {0}\n{1}", requestHintText, memberRequest.RequestString); switch (request.QueryType) { case QueryType.Instances: entitiesData = svc.GetEntitiesByType(entityRefs.Single( ), true, memberRequest); break; case QueryType.ExactInstances: entitiesData = svc.GetEntitiesByType(entityRefs.Single( ), false, memberRequest); break; case QueryType.Basic: case QueryType.BasicWithDemand: entitiesData = svc.GetEntitiesData(entityRefs, memberRequest); break; default: throw new ArgumentException(string.Format("Unknown query type {0}", request.QueryType)); } } } catch (ArgumentException ex) { // sorry world! if (ex.Message.Contains("does not represent a known entity")) { singleResult.Code = HttpStatusCode.BadRequest; continue; } throw; } // Skip results where access is denied foreach (EntityData entityData in entitiesData.Where(ed => ed != null)) { AppendEntityToResult(result, entityData); singleResult.Ids.Add(entityData.Id.Id); } // Set the result to NotFound for Basic and BasicWithDemand only if (request.QueryType == QueryType.Basic || request.QueryType == QueryType.BasicWithDemand) { singleResult.Code = singleResult.Ids.Count > 0 ? HttpStatusCode.OK : HttpStatusCode.NotFound; } else { singleResult.Code = HttpStatusCode.OK; } } catch (PlatformSecurityException ex) { EventLog.Application.WriteWarning(ex.ToString( )); singleResult.Ids.Clear( ); singleResult.Code = HttpStatusCode.Forbidden; } catch (Exception ex) { EventLog.Application.WriteError(ex.ToString( )); singleResult.Code = HttpStatusCode.InternalServerError; } finally { // Place in the finally block so we are certain that it gets run exactly once per iteration result.Results.Add(singleResult); } } if (result.Results.Count != batch.Requests.Length) { // We are indexing by ID, so we must always ensure that request position matches result position. throw new Exception("Internal error: batch query mismatch."); } }
/// <summary> /// Gets the form data. /// </summary> /// <param name="request">The request.</param> /// <returns></returns> public FormDataResponse GetFormData(FormDataRequest request) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var entityIdRef = new EntityRef(request.EntityId); // Get the entity data EntityData entityData = BulkRequestRunner.GetEntityData(entityIdRef, request.Query, request.Hint); // Set the result to NotFound for Basic and BasicWithDemand only if (entityData == null) { return(null); } var formId = new EntityRef(request.FormId).Id; EntityData formEntityData = null; // Get the form entity data for non gen forms if (!EntityTemporaryIdAllocator.IsAllocatedId(formId)) { try { formEntityData = GetFormAsEntityData(formId, false); } catch (Exception ex) { EventLog.Application.WriteError("Failed to get form with id {0}. Unable to get initial form control visibility. Error: {1}.", formId, ex); } } ISet <long> initiallyHiddenControls = null; if (formEntityData == null) { return(PackageFormDataResponse(entityData, null)); } IDictionary <long, IExpression> compiledExpressions = null; IDictionary <long, string> controlVisibilityCalculations; using (new SecurityBypassContext()) { // Have form. Get any visibility calculations controlVisibilityCalculations = GetControlVisibilityCalculations(formEntityData); long entityTypeId = GetTypeToEditWithForm(formEntityData); if (controlVisibilityCalculations.Count > 0 && entityTypeId > 0) { // Now we have all the calculations compiledExpressions = CompileVisibilityCalculations(new EntityRef(entityTypeId), controlVisibilityCalculations); } } if (controlVisibilityCalculations.Count > 0) { initiallyHiddenControls = GetHiddenControls(entityIdRef.Entity, controlVisibilityCalculations.Keys, compiledExpressions); } return(PackageFormDataResponse(entityData, initiallyHiddenControls)); }
/// <summary> /// Gets the identity providers for the specified tenant. /// </summary> /// <returns>TenantIdentityProviderResponse.</returns> public TenantIdentityProviderResponse GetIdentityProviders(string tenant) { if (string.IsNullOrWhiteSpace(tenant)) { throw new ArgumentNullException(nameof(tenant)); } var response = new TenantIdentityProviderResponse { IdentityProviders = new List <IdentityProviderResponse>() }; using (new SecurityBypassContext()) { TenantAdministratorContext tenantAdminContext; try { tenantAdminContext = new TenantAdministratorContext(tenant); } catch (EntityNotFoundException) { return(response); } try { var idProviderId = new EntityRef("core:identityProvider"); var isOfTypeId = new EntityRef("core:isOfType"); var nameId = new EntityRef("core:name"); var aliasId = new EntityRef("core:alias"); var isEnabledId = new EntityRef("core:isProviderEnabled"); var ordinalId = new EntityRef("core:providerOrdinal"); // Get the identity providers for the given tenant var entityRequest = new EntityRequest { Entities = idProviderId.ToEnumerable(), RequestString = "name, isProviderEnabled, providerOrdinal, isOfType.alias", Hint = "tenant ip providers", QueryType = QueryType.Instances }; // Run the query var instances = BulkRequestRunner.GetEntitiesByType(entityRequest); if (instances == null) { return(response); } foreach (var instance in instances) { var isEnabled = false; string name = null; var ordinal = 0; var id = instance.Id.Id; foreach (var fieldData in instance.Fields) { if (fieldData.FieldId == null || fieldData.Value?.Type == null) { continue; } if (fieldData.FieldId.Id == nameId.Id) { name = fieldData.Value.ValueString; } else if (fieldData.FieldId.Id == ordinalId.Id && fieldData.Value.Type.GetRunTimeType() == typeof(int) && fieldData.Value.Value != null) { ordinal = (int)fieldData.Value.Value; } else if (fieldData.FieldId.Id == isEnabledId.Id && fieldData.Value.Type.GetRunTimeType() == typeof(bool) && fieldData.Value.Value != null) { isEnabled = (bool)fieldData.Value.Value; } } if (!isEnabled || string.IsNullOrWhiteSpace(name)) { continue; } var singleOrDefault = instance.Relationships.SingleOrDefault(r => r.RelationshipTypeId.Id == isOfTypeId.Id); if (singleOrDefault == null) { continue; } var isOfTypeEntityData = singleOrDefault.Entities; var identityProvider = new IdentityProviderResponse { Id = id, Name = name, Ordinal = ordinal }; if (isOfTypeEntityData != null) { var typeEntity = isOfTypeEntityData.FirstOrDefault(); if (typeEntity == null) { continue; } var typeAliasData = typeEntity.Fields.SingleOrDefault(f => f.FieldId.Id == aliasId.Id); if (typeAliasData?.Value != null) { identityProvider.TypeAlias = typeAliasData.Value.ValueString; } } response.IdentityProviders.Add(identityProvider); } } finally { tenantAdminContext.Dispose(); } } return(response); }
/// <summary> /// Sets the default reports for relationship controls. /// </summary> /// <param name="form">The form.</param> private static void SetDefaultReportsForRelationshipControls(EntityData form) { // Find the template report var relationshipDisplayReportRelId = Entity.GetId("console:relationshipDisplayReport"); var pickerReportRelId = Entity.GetId("console:pickerReport"); var isReversedFieldId = Entity.GetId("console:isReversed"); var relationshipToRenderRelId = Entity.GetId("console:relationshipToRender"); // get all Inline and tabbed relationships var relationshipControls = new List <EntityData>(); GetAllRelationshipControlsOnForm(form, relationshipControls); // list of reports that we need to load - mapped to relationship containers that we need to place them in var reportsToLoad = new List <Tuple <EntityRef, RelationshipData> >(); foreach (EntityData relationshipControl in relationshipControls) { // get the relationship that this control represents var relationshipToRenderRelation = GetRelationDataByRelationId(relationshipToRenderRelId, relationshipControl.Relationships); if (relationshipToRenderRelation == null || relationshipToRenderRelation.Instances.Count <= 0) { continue; } var relationshipId = relationshipToRenderRelation.Instances.First().Entity.Id; var relationship = Entity.Get <Relationship>(relationshipId); // get the direction that we're following the relationship bool isReversed = false; var isReversedField = GetFieldDataByFieldId(isReversedFieldId, relationshipControl.Fields); if (isReversedField != null && isReversedField.Value.Value != null) { bool.TryParse(isReversedField.Value.Value.ToString(), out isReversed); } // check display report CheckRelationshipReport(relationshipControl, relationshipDisplayReportRelId, relationship, isReversed, reportsToLoad, true); // check picker report CheckRelationshipReport(relationshipControl, pickerReportRelId, relationship, isReversed, reportsToLoad, false); } // load reports if (reportsToLoad.Count > 0) { var reportIds = reportsToLoad.Select(r => r.Item1).Distinct(); var reports = BulkRequestRunner.GetEntitiesData(reportIds, CustomEditFormHelper.GetRelationshipReportQueryString, "Form report"); var reportDict = reports.ToDictionarySafe(r => r.Id.Id); foreach (var task in reportsToLoad) { EntityRef reportId = task.Item1; EntityData reportData = reportDict[reportId.Id]; if (reportData != null) { // store data in the relationship container RelationshipData container = task.Item2; container.Instances.Add( new RelationshipInstanceData() { DataState = DataState.Unchanged, Entity = reportData }); } } } }
/// <summary> /// Check whether the user has access to the entities by following relationships where the /// <see cref="Relationship"/> type has the <see cref="Relationship.SecuresTo"/> or /// <see cref="Relationship.SecuresFrom"/> flag set. /// </summary> /// <param name="permissions"> /// The permissions to check for. This cannot be null or contain null. /// </param> /// <param name="user"> /// The user to do the check access for. This cannot be null. /// </param> /// <param name="entitiesToCheck"> /// The entities to check. This cannot be null or contain null. /// </param> /// <exception cref="ArgumentNullException"> /// No argument can be null. /// </exception> internal ISet <EntityRef> CheckAccessControlByRelationship(IList <EntityRef> permissions, EntityRef user, ISet <EntityRef> entitiesToCheck) { if (permissions == null) { throw new ArgumentNullException("permissions"); } if (permissions.Contains(null)) { throw new ArgumentNullException("permissions", "Cannot contain null"); } if (user == null) { throw new ArgumentNullException("user"); } if (entitiesToCheck == null) { throw new ArgumentNullException("entitiesToCheck"); } EntityMemberRequest entityMemberRequest; IDictionary <long, ISet <EntityRef> > entityTypes; IEnumerable <EntityData> entitiesData; IDictionary <long, bool> accessToRelatedEntities; HashSet <EntityRef> result; IList <long> relatedAccessibleEntities; EntityType entityType; using (Profiler.MeasureAndSuppress("SecuresFlagEntityAccessControlChecker.CheckAccessControlByRelationship")) { result = new HashSet <EntityRef>(EntityRefComparer.Instance); entityTypes = TypeRepository.GetEntityTypes(entitiesToCheck); using (MessageContext outerMessageContext = new MessageContext(EntityAccessControlService.MessageName)) foreach (KeyValuePair <long, ISet <EntityRef> > entitiesType in entityTypes) { outerMessageContext.Append(() => string.Format( "Checking relationships for entity(ies) \"{0}\" of type \"{1}\":", string.Join(", ", entitiesType.Value.Select(er => string.Format("'{0}' ({1})", er.Entity.As <Resource>().Name, er.Id))), string.Join(", ", string.Format("'{0}' ({1})", Entity.Get <Resource>(entitiesType.Key).Name, entitiesType.Key)))); using (MessageContext innerMessageContext = new MessageContext(EntityAccessControlService.MessageName)) { entityType = Entity.Get <EntityType>(entitiesType.Key); if (entityType != null) { entityMemberRequest = EntityMemberRequestFactory.BuildEntityMemberRequest(entityType, permissions); if (entityMemberRequest.Relationships.Count > 0) { IList <EntityRef> relatedEntitiesToCheck; innerMessageContext.Append(() => string.Format("Security relationship structure for entity type '{0}':", entityType.Id)); TraceEntityMemberRequest(entityMemberRequest); // Get the IDs of entities to check security for EntityRequest request = new EntityRequest { Entities = entitiesType.Value, Request = entityMemberRequest, IgnoreResultCache = true // security engine does its own result caching }; entitiesData = BulkRequestRunner.GetEntitiesData(request).ToList( ); // Do a single security check for all entities related to // the entities passed in, excluding the original entities // that failed the security check. relatedEntitiesToCheck = Delegates .WalkGraph( entitiesData, entityData => entityData.Relationships.SelectMany(relType => relType.Entities)) .Select(ed => ed.Id) .Where(er => !entitiesType.Value.Contains(er, EntityRefComparer.Instance)) .ToList(); if (relatedEntitiesToCheck.Count > 0) { // Add the relationship types to watch for cache invalidations using (CacheContext cacheContext = CacheContext.GetContext()) { IList <long> relationshipTypes = Delegates .WalkGraph(entityMemberRequest.Relationships, rr => rr.RequestedMembers.Relationships) .Select(rr => rr.RelationshipTypeId.Id) .ToList(); cacheContext.RelationshipTypes.Add(relationshipTypes); } // ReSharper disable AccessToModifiedClosure // Do a single access check for all entities for efficiency, then pick the // important ones for each requested entity below. accessToRelatedEntities = null; innerMessageContext.Append( () => string.Format( "Checking related entities '{0}':", string.Join(", ", relatedEntitiesToCheck.Select(er => er.Id)))); SecurityBypassContext.RunAsUser( () => accessToRelatedEntities = Checker.CheckAccess(relatedEntitiesToCheck, permissions, user)); // ReSharper restore AccessToModifiedClosure foreach (EntityData entityData in entitiesData) { // Get the related entities to check relatedEntitiesToCheck = Delegates.WalkGraph( entityData, ed => ed.Relationships.SelectMany(relType => relType.Entities)) .Select(ed => ed.Id) .ToList(); // Remove the start entity for the query, since security has // already been checked on it. relatedEntitiesToCheck.Remove(entityData.Id); // Get the related entities the user has access to relatedAccessibleEntities = accessToRelatedEntities .Where(kvp => kvp.Value && relatedEntitiesToCheck.Contains(kvp.Key, EntityRefComparer.Instance)) .Select(kvp => kvp.Key) .ToList(); // Grant access if the user has access to ANY of the related // entities. if (relatedEntitiesToCheck.Count > 0 && relatedAccessibleEntities.Count > 0) { result.Add(entityData.Id); // ReSharper disable AccessToModifiedClosure innerMessageContext.Append( () => string.Format( "Access to '{0}' granted due to corresponding access to '{1}'", string.Join(", ", relatedEntitiesToCheck.Select(id => string.Format("'{0}' ({1})", Entity.Get <Resource>(id).Name, id))), string.Join(", ", relatedAccessibleEntities.Select(id => string.Format("'{0}' ({1})", Entity.Get <Resource>(id).Name, id))))); // ReSharper restore AccessToModifiedClosure } } } } else { innerMessageContext.Append(() => string.Format("No relationships found to check for entity type '{0}'.", entityType.Id)); } } else { EventLog.Application.WriteWarning("Type ID {0} for entities '{1}' is not a type", entitiesType.Key, string.Join(", ", entitiesType.Value)); } } } } return(result); }