/// <summary> /// Check whether the user has access to the entities by following relationship types where the /// <see cref="Relationship"/> type has the <see cref="Relationship.SecuresTo"/> or /// <see cref="Relationship.SecuresFrom"/> flag set. /// </summary> /// <param name="permission"> /// The type of permission to follow relationships for. /// </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> CheckAccessControlByRelationshipType(EntityRef permission, EntityRef user, ISet <EntityRef> entitiesToCheck) { if (permission == null) { throw new ArgumentNullException(nameof(permission)); } if (user == null) { throw new ArgumentNullException(nameof(user)); } if (entitiesToCheck == null) { throw new ArgumentNullException(nameof(entitiesToCheck)); } ; IDictionary <long, ISet <EntityRef> > entitiesOfType; HashSet <EntityRef> result; result = new HashSet <EntityRef>(EntityRefComparer.Instance); entitiesOfType = TypeRepository.GetEntityTypes(entitiesToCheck); foreach (KeyValuePair <long, ISet <EntityRef> > entitiesType in entitiesOfType) { EntityType entityType; entityType = Entity.Get <EntityType>(entitiesType.Key); if (entityType != null) { if (CheckTypeAccess(entityType, permission, user)) { result.UnionWith(entitiesType.Value); } } else { EventLog.Application.WriteWarning("Type ID {0} for entities '{1}' is not a type", entitiesType.Key, string.Join(", ", entitiesType.Value)); } } return(result); }
/// <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); }