bool EvaluateForwardTriggers(IResourceTriggerFilterPolicyCache policyCache, IEntity entity, IEnumerable <long> changedFields, IEnumerable <long> changedRels, IEnumerable <long> changedRevRels, List <Action> postSaveActions) { using (Profiler.Measure("ResourceTriggerFilterEventTarget.EvaluateForwardTriggers")) { var typeId = entity.TypeIds.FirstOrDefault(); if (typeId != 0) { List <ResourceTriggerFilterDef> policies; if (policyCache.TypePolicyMap.TryGetValue(typeId, out policies)) { if (policies.Any()) { // we match on type var allChanges = changedFields.Union(changedRels).Union(changedRevRels); // // Check if our entity matches any of the fields and rels foreach (var policy in policies) { if (policy == null) { continue; // assert false } var watchedFields = policyCache.PolicyToFieldsMap[policy.Id]; if (!watchedFields.Any() || watchedFields.Intersect(allChanges).Any()) { var handler = policyCache.PolicyTypeHandlerMap[policy.TypeIds.First()]; if (handler != null) { var isNew = entity.IsTemporaryId; bool result = false; SecurityBypassContext.RunAsUser(() => { if (handler.OnBeforeSave(policy, entity, isNew, changedFields, changedRels, changedRevRels)) { result = true; } else { AddWrappedPostSaveAction(postSaveActions, () => handler.OnAfterSave(policy, entity, isNew, changedFields, changedRels, changedRevRels)); } }); if (result) { return(true); // We failed, so bug out } } } } } } } } return(false); }
/// <summary> /// Is there an access rule for the specified type(s) that includes the requested permission? E.g. create. /// </summary> /// <param name="entityType"> /// The <see cref="EntityType"/> to check. This cannot be null. /// </param> /// <param name="permission">The permission being sought.</param> /// <param name="user"> The user requesting access. This cannot be null. </param> /// <returns> /// True if the user can create entities of that type, false if not. /// </returns> private bool CheckTypeAccess(EntityType entityType, EntityRef permission, EntityRef user) { if (user == null) { throw new ArgumentNullException(nameof(user)); } if (entityType == null) { throw new ArgumentNullException(nameof(entityType)); } List <EntityType> entityTypesToCheck; bool result; using (new SecurityBypassContext()) using (Profiler.MeasureAndSuppress("SecuresFlagEntityAccessControlChecker.CheckTypeAccess")) { // Check if the type itself can be created using (MessageContext messageContext = new MessageContext(EntityAccessControlService.MessageName)) { messageContext.Append(() => "Checking access rules first:"); IDictionary <long, bool> resultAsDict = null; List <EntityType> entityTypeAsList = new List <EntityType> { entityType }; SecurityBypassContext.RunAsUser(() => resultAsDict = Checker.CheckTypeAccess(entityTypeAsList, permission, user)); result = resultAsDict [entityType.Id]; if (result) { return(result); } } // Walk graph to get list of types to check entityTypesToCheck = ReachableTypes(entityType.ToEnumerable( ), true, false) .Select(walkStep => walkStep.Node) .Where(et => et != entityType) // skip root type. .ToList( ); result = CheckTypeAccessRelatedTypesImpl(entityType, permission, user, entityTypesToCheck); if (!result) { return(false); } // Register cache invalidations using (CacheContext cacheContext = CacheContext.GetContext( )) { cacheContext.EntityTypes.Add(WellKnownAliases.CurrentTenant.Relationship); cacheContext.FieldTypes.Add(WellKnownAliases.CurrentTenant.SecuresFrom, WellKnownAliases.CurrentTenant.SecuresTo, WellKnownAliases.CurrentTenant.SecuresFromReadOnly, WellKnownAliases.CurrentTenant.SecuresToReadOnly); } } return(result); }
public void Test_RunAsUser() { bool elevated; elevated = false; SecurityBypassContext.RunAsUser(() => elevated = SecurityBypassContext.IsActive); Assert.That(elevated, Is.False, "Elevated"); }
/// <summary> /// Get the value of an access control field. /// </summary> /// <param name="entityId">The entityId</param> /// <param name="fieldId">The fieldId</param> /// <returns>A A typed value.</returns> internal static TypedValue TryGetAccessControlField(long entityId, IEntityRef fieldId) { var value = new TypedValue { Type = DatabaseType.BoolType }; if (entityId < 1) { throw new ArgumentNullException("entityId"); } if (fieldId == null || fieldId.Namespace != "core") { throw new ArgumentException("fieldId: invalid namespace"); } switch (fieldId.Alias) { case "canCreateType": { var entityType = Entity.Get <EntityType>(entityId); if (entityType == null) { throw new InvalidOperationException("Assert false: attempted to load an entity as an EntityType which is not a type."); } SecurityBypassContext.RunAsUser(() => { value.Value = AccessControlService.CanCreate(entityType); }); break; } case "canModify": { SecurityBypassContext.RunAsUser(() => { value.Value = AccessControlService.Check(entityId, new[] { Permissions.Modify }); }); break; } case "canDelete": { SecurityBypassContext.RunAsUser(() => { value.Value = AccessControlService.Check(entityId, new[] { Permissions.Delete }); }); break; } default: { // return null value break; } } return(value); }
/// <summary> /// Perform a create using the provided update action. To be used for testing. /// </summary> /// <param name="resType"></param> /// <param name="updateAction"></param> public static IEntity PerformCreate(EntityType resType, Action <IEntity> updateAction) { try { IEntity newEntity = null; SecurityBypassContext.RunAsUser(() => { newEntity = Entity.Create(resType); var provider = Factory.Current.Resolve <IEntityDefaultsDecoratorProvider>(); var decorator = provider.GetDefaultsDecorator(resType.Id); // Do per-entity work decorator.SetDefaultValues(newEntity.ToEnumerable()); }); updateAction(newEntity); SecurityBypassContext.RunAsUser(() => { newEntity.Save(); }); return(newEntity); } catch (ValidationException ex) { string message = ex.FieldName != null?string.Format("{0}: {1}", ex.FieldName, ex.Message) : ex.Message; throw new WorkflowRunException(message, ex); } catch (CardinalityViolationException ex) { throw new WorkflowRunException(ex.Message, ex); } catch (DuplicateKeyException ex) { throw new WorkflowRunException(ex.Message, ex); } catch (InvalidCastException ex) { throw new WorkflowRunException("Incorrect data type", ex); } catch (SqlException ex) { if (ex.Number == 547) { throw new CreateInvalidReferenceException(ex); } else { throw; } } }
public void Test_NoBypassContext() { bool elevated; elevated = false; SecurityBypassContext.Elevate(() => elevated = SecurityBypassContext.IsActive); Assert.That(elevated, Is.True, "Elevate is not elevated"); SecurityBypassContext.RunAsUser(() => elevated = SecurityBypassContext.IsActive); Assert.That(elevated, Is.False, "RunAsUser is elevated"); }
/// <summary> /// Update all the the provided expressions. /// </summary> private Dictionary <WfExpression, object> ResolveExpressions(IRunState context, ICollection <WfExpression> expressions) { Dictionary <WfExpression, object> result = null; // evaluate the expressions and update the targeted values SecurityBypassContext.RunAsUser(() => { result = expressions.ToDictionary(e => e, e => EvaluateExpression(context, e)); }); return(result); }
bool OnBeforeRelsThisEnd(IEnumerable <IEntity> entities, List <Action> postSaveActions) { using (Profiler.Measure("ResourceTriggerFilterEventTarget.OnBeforeDelete OnBeforeRelsThisEnd")) { var _policy = Factory.ResourceTriggerFilterPolicyCache; foreach (var entity in entities) { var typeId = entity.TypeIds.FirstOrDefault(); if (typeId != 0) { var policies = _policy.TypePolicyMap[typeId]; if (policies != null) { foreach (var policy in policies) { var handler = _policy.PolicyTypeHandlerMap[policy.TypeIds.First()]; if (handler != null) { var isNew = entity.IsTemporaryId; bool result = false; SecurityBypassContext.RunAsUser(() => { if (handler.OnBeforeDelete(policy, entity)) { result = true; } else { postSaveActions.Add(() => handler.OnAfterDelete(policy, entity)); } }); if (result) { return(true); // We failed, so bug out } } } } } } return(false); } }
void IRunNowActivity.OnRunNow(IRunState context, ActivityInputs inputs) { var ResourceToUpdateKey = GetArgumentKey("setChoiceActivityResourceArgument"); var ValueKey = GetArgumentKey("setChoiceActivityValueArgument"); var FieldKey = GetArgumentKey("setChoiceActivityFieldArgument"); var replaceExistingKey = GetArgumentKey("setChoiceActivityReplaceExisting"); var choiceToUpdateRef = (IEntity)inputs[FieldKey]; var resId = (IEntity)inputs[ResourceToUpdateKey]; var valueId = (IEntity)inputs[ValueKey]; bool replaceExisting = false; object replaceExistingObj; if (inputs.TryGetValue(replaceExistingKey, out replaceExistingObj)) { replaceExisting = (bool?)replaceExistingObj ?? false; } var relationship = choiceToUpdateRef.As <Relationship>(); SecurityBypassContext.RunAsUser(() => { var resource = resId.AsWritable(); // Does not have to be a writeable copy because we are only manipulating relationships. var cardinality = relationship.Cardinality_Enum ?? CardinalityEnum_Enumeration.ManyToMany; replaceExisting = replaceExisting || cardinality == CardinalityEnum_Enumeration.OneToOne || cardinality == CardinalityEnum_Enumeration.ManyToOne; var relCollection = resource.GetRelationships(choiceToUpdateRef, Direction.Forward); // you can't have a reverse choice field. if (replaceExisting) { relCollection.Clear(); } relCollection.Add(valueId); resource.Save(); }); }
public void Test_OneBypassContext() { bool elevated; elevated = false; using (new SecurityBypassContext()) { Assert.That(SecurityBypassContext.IsActive, Is.True, "Not active"); SecurityBypassContext.Elevate(() => elevated = SecurityBypassContext.IsActive); Assert.That(elevated, Is.True, "Elevate is not elevated"); SecurityBypassContext.RunAsUser(() => elevated = SecurityBypassContext.IsActive); Assert.That(elevated, Is.False, "RunAsUser is elevated"); } }
void IRunNowActivity.OnRunNow(IRunState context, ActivityInputs inputs) { var typeRefKey = GetArgumentKey("getResourcesResourceType"); var reportRefKey = GetArgumentKey("getResourcesReport"); var listKey = GetArgumentKey("getResourcesList"); var firstKey = GetArgumentKey("getResourcesFirst"); var countKey = GetArgumentKey("getResourcesCount"); object o; EntityType resourceType = null; IEntity reportRef = null; if (inputs.TryGetValue(typeRefKey, out o)) { if (o != null) { resourceType = ((IEntity)o).As <EntityType>(); } } if (inputs.TryGetValue(reportRefKey, out o)) { if (o != null) { reportRef = (IEntity)o; } } if (resourceType == null && reportRef == null) { throw new WorkflowRunException("Get Resources must have one of either the Type or Report parameters specified."); } IEnumerable <IEntity> list = null; SecurityBypassContext.RunAsUser(() => { list = reportRef != null ? GetListFromReport(context, reportRef) : GetListFromType(resourceType); }); context.SetArgValue(ActivityInstance, listKey, list); context.SetArgValue(ActivityInstance, firstKey, list.FirstOrDefault()); context.SetArgValue(ActivityInstance, countKey, list.Count()); }
void IRunNowActivity.OnRunNow(IRunState context, ActivityInputs inputs) { var resourceToDeleteKey = GetArgumentKey("deleteActivityResourceArgument"); var res = (IEntity)inputs[resourceToDeleteKey]; if (res == null) { throw new WorkflowRunException_Internal("No record provided.", null); } SecurityBypassContext.RunAsUser(() => { Entity.Delete(res.Id); }); context.RemoveReference(res.Id); }
private bool CheckTypeAccessRelatedTypesImpl(EntityType entityType, EntityRef permission, EntityRef user, List <EntityType> entityTypesToCheck) { bool result = false; using (MessageContext messageContext = new MessageContext(EntityAccessControlService.MessageName)) { IDictionary <long, bool> canAccessTypes; string message; // Allow access if the user has access to any of the related types if (entityTypesToCheck.Count == 0) { messageContext.Append(() => "No entity types found to check."); return(false); } // Check whether the user has access to the given types canAccessTypes = null; SecurityBypassContext.RunAsUser(() => canAccessTypes = Checker.CheckTypeAccess( entityTypesToCheck.ToList( ), permission, user)); message = string.Format( "Checking security relationship(s) to see whether user can create entities of type '{0}': ", entityType.Name ?? entityType.Id.ToString( )); if (canAccessTypes.Any(kvp => kvp.Value)) { messageContext.Append(() => string.Format( "{0} Allowed due to create access to entity type(s) '{1}'", message, canAccessTypes.Select(kvp => entityTypesToCheck.First(et => et.Id == kvp.Key).Name ?? kvp.Key.ToString( )))); result = true; } else { messageContext.Append(() => $"{message} Denied"); } } return(result); }
/// <summary> /// Perform a update using the provided update action. To be used for testing. /// </summary> /// <param name="entityToUpdate"></param> /// <param name="updateAction"></param> public static void PerformUpdate(IEntity entityToUpdate, Action <IEntity> updateAction) { try { updateAction(entityToUpdate); SecurityBypassContext.RunAsUser(() => { entityToUpdate.Save(); }); } catch (EDC.ReadiNow.Model.ValidationException ex) { string message = ex.FieldName != null?string.Format("{0}: {1}", ex.FieldName, ex.Message) : ex.Message; throw new WorkflowRunException(message, ex); } catch (CardinalityViolationException ex) { throw new WorkflowRunException(ex.Message, ex); } catch (DuplicateKeyException ex) { throw new WorkflowRunException(ex.Message, ex); } catch (InvalidCastException ex) { throw new WorkflowRunException("Incorrect data type", ex); } catch (SqlException ex) { if (ex.Number == 547) { throw new UpdateInvalidReferenceException(ex); } else { throw; } } }
public void Test_RunAsUser_NullAction() { Assert.That(() => SecurityBypassContext.RunAsUser(null), Throws.TypeOf <ArgumentNullException>().And.Property("ParamName").EqualTo("action")); }
/// <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); }
void IRunNowActivity.OnRunNow(IRunState context, ActivityInputs inputs) { var OriginKey = GetArgumentKey("setRelationshipActivityOriginArgument"); var DestinationKey = GetArgumentKey("setRelationshipActivityDestinationArgument"); var RelationshipKey = GetArgumentKey("setRelationshipActivityRelationshipArgument"); var replaceExistingKey = GetArgumentKey("setRelationshipActivityReplaceExisting"); var isReverseKey = GetArgumentKey("setRelationshipActivityIsReverse"); var originRef = (IEntity)inputs[OriginKey]; var relationshipRef = (IEntity)inputs[RelationshipKey]; IEntity destinationRef = null; object destinationRefObj; if (inputs.TryGetValue(DestinationKey, out destinationRefObj)) { destinationRef = (IEntity)destinationRefObj; } bool replaceExisting = false; object replaceExistingObj; if (inputs.TryGetValue(replaceExistingKey, out replaceExistingObj)) { replaceExisting = (bool?)replaceExistingObj ?? false; } var direction = Direction.Forward; object isReverseObj; if (inputs.TryGetValue(isReverseKey, out isReverseObj)) { direction = ((bool?)isReverseObj ?? false) ? Direction.Reverse : Direction.Forward; } var relationship = relationshipRef.As <Relationship>(); SecurityBypassContext.RunAsUser(() => { var origin = originRef.AsWritable <Entity>(); var cardinality = relationship.Cardinality_Enum ?? CardinalityEnum_Enumeration.ManyToMany; replaceExisting = replaceExisting || cardinality == CardinalityEnum_Enumeration.OneToOne || (direction == Direction.Forward && cardinality == CardinalityEnum_Enumeration.ManyToOne) || (direction == Direction.Reverse && cardinality == CardinalityEnum_Enumeration.OneToMany) ; var relCollection = origin.GetRelationships(relationshipRef, direction); if (replaceExisting) { relCollection.Clear(); } if (destinationRef != null) { relCollection.Add(destinationRef); } origin.Save(); }); }
/// <summary> /// Update a workflow using a update action. This will take care of handling paused instances by cloning. /// </summary> /// <param name="workflowId"></param> /// <param name="updateAction"></param> /// <returns>True if the workflow needed to be clones</returns> public static bool Update(long workflowId, Action updateAction) { using (new SecurityBypassContext(true)) { Workflow clone = null; var original = Entity.Get <Workflow>(workflowId); if (original == null) { throw new WebArgumentException("The provided Workflow Id is not a workflow"); } if (original.WfNewerVersion != null) { throw new WebArgumentException("This Workflow is not the latest version so cannot be updated"); } using (var databaseContext = DatabaseContext.GetContext(true)) { var havePausedInstances = original.RunningInstances.Any(r => r.WorkflowRunStatus_Enum == WorkflowRunState_Enumeration.WorkflowRunPaused); IDictionary <long, long> clonedIds = null; if (havePausedInstances) { // clone the workflow (Need to save because the clone occurs in the DB) clone = original.Clone <Workflow>(); clonedIds = clone.Save(); // There seems to be a bug in entity clone that means you can't remove solution till after it has been saved. clone = clone.AsWritable <Workflow>(); clone.InSolution = null; // Fix up the version linking - again we can't seem to do it as a single save. var originalWfOlderVersionId = original.WfOlderVersion != null ? original.WfOlderVersion.Id : -1; original = original.AsWritable <Workflow>(); original.WfOlderVersion = clone; original.Save(); if (originalWfOlderVersionId != -1) { clone.WfOlderVersion = Entity.Get <Workflow>(originalWfOlderVersionId); } clone.Save(); } SecurityBypassContext.RunAsUser(updateAction); original = original.AsWritable <Workflow>(); original.WorkflowUpdateHash = _randGen.Next().ToString(CultureInfo.InvariantCulture); // ideally this should be a hash of only the important aspects of the workflow original.WorkflowVersion = (original.WorkflowVersion ?? 1) + 1; if (havePausedInstances) { original.WfOlderVersion = clone; WorkflowRunHelper.MoveRuns(original, clone, clonedIds); } original.Save(); // This will also save the close databaseContext.CommitTransaction(); return(havePausedInstances); } } }
bool OnBeforeRelsOtherEnds(IEnumerable <IEntity> entities, List <Action> postSaveActions) { using (Profiler.Measure("ResourceTriggerFilterEventTarget.OnBeforeDelete OnBeforeRelsOtherEnds")) { var policyCache = Factory.ResourceTriggerFilterPolicyCache; foreach (var entity in entities) { // // Get the filtered list of relationships that could possibly apply to some of the related entities // var filteredRels = GetRelsWithPotentialPolicies(policyCache, entity).ToList(); if (!filteredRels.Any()) { continue; } // Prefill the cache var requestString = string.Join(",", filteredRels.Select(r => (r.Item2 == Direction.Reverse ? "-#" : "#") + r.Item1.Id + ".id")); BulkPreloader.Preload(new EntityRequest(entity.Id, requestString)); // // Check each entity on the other end of the filtered rels t see if any policies apply foreach (var relDir in filteredRels) { var rel = relDir.Item1; var direction = relDir.Item2; var relInstances = entity.GetRelationships(rel, direction); foreach (var otherEntity in relInstances) { var otherType = otherEntity.TypeIds.First(); var policies = policyCache.TypePolicyMap[otherType].Where(p => p.UpdatedRelationshipsToTriggerOn.Contains(rel, EntityIdEqualityComparer <Relationship> .Instance)); foreach (var policy in policies) { var handler = policyCache.PolicyTypeHandlerMap[policy.TypeIds.First()]; bool result = false; SecurityBypassContext.RunAsUser(() => { if (handler.OnBeforeReverseRemove(policy, rel.Id, direction.Reverse(), otherEntity, entity)) { result = true; } else { AddWrappedPostSaveAction(postSaveActions, () => handler.OnAfterReverseRemove(policy, rel.Id, direction.Reverse(), otherEntity, entity)); } }); if (result) { return(true); } } } } } return(false); } }
bool EvaluateReverseTriggers(IResourceTriggerFilterPolicyCache policyCache, IEntity entity, IEnumerable <long> changedFields, IEnumerable <long> changedRels, IEnumerable <long> changedRevRels, List <Action> postSaveActions) { using (Profiler.Measure("ResourceTriggerFilterEventTarget.EvaluateReverseTriggers")) { // // Check if any of the related changed resources match any of our policies // TODO: Can we filter this earlier so I don't have to grab the type of every other end? var allChangedRels = changedRels.Select(rId => new { Id = rId, Direction = Direction.Forward }).Union(changedRevRels.Select(rId => new { Id = rId, Direction = Direction.Reverse })); var relChanges = allChangedRels.ToDictionary(rd => rd, rd => entity.GetRelationships(rd.Id, rd.Direction)); // TODO: Can we make sure that all the type information for the entities is grabbed in a single hit? (It might already be happening) foreach (var kvp in relChanges) { var relId = kvp.Key.Id; var direction = kvp.Key.Direction; var otherEntities = kvp.Value; using (Profiler.Measure("ResourceTriggerFilterEventTarget.EvaluateReverseTriggers adds")) { // // Test for any added entities that a policy may apply to // var added = GetAdded(otherEntities); foreach (var otherEntity in added) { if (otherEntity != null) { bool result = false; TestOtherEndPolicyAndAct(policyCache, relId, direction, entity, otherEntity, (policy) => { var handler = policyCache.PolicyTypeHandlerMap[policy.TypeIds.First()]; SecurityBypassContext.RunAsUser(() => { if (handler.OnBeforeReverseAdd(policy, relId, direction.Reverse(), otherEntity, entity, entity.IsTemporaryId)) { result = true; } else { AddWrappedPostSaveAction(postSaveActions, () => handler.OnAfterReverseAdd(policy, relId, direction.Reverse(), otherEntity, entity, entity.IsTemporaryId)); } }); }); if (result) { return(true); // It failed so bug out } } } } using (Profiler.Measure("ResourceTriggerFilterEventTarget.EvaluateReverseTriggers removes")) { // // Test for any removed policies that might apply to // var removed = GetRemoved(otherEntities); foreach (var otherEntity in removed) { if (otherEntity != null) { bool result = false; TestOtherEndPolicyAndAct(policyCache, relId, direction, entity, otherEntity, (policy) => { var handler = policyCache.PolicyTypeHandlerMap[policy.TypeIds.First()]; SecurityBypassContext.RunAsUser(() => { if (handler.OnBeforeReverseRemove(policy, relId, direction.Reverse(), otherEntity, entity)) { result = true; } else { AddWrappedPostSaveAction(postSaveActions, () => handler.OnAfterReverseRemove(policy, relId, direction.Reverse(), otherEntity, entity)); } }); }); if (result) { return(true); // It failed so bug out } } } } } } return(false); }