Example #1
0
        /// <summary>
        /// Generate the query SQL. Do not actually run it.
        /// </summary>
        /// <param name="query">The structured query object to convert.</param>
        /// <param name="settings">Build-time settings for the conversion.</param>
        /// <returns>An object structure containing SQL, and other discovered information.</returns>
        public QueryBuild BuildSql(StructuredQuery query, QuerySqlBuilderSettings settings)
        {
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }

            // Initialise settings
            if (settings == null)
            {
                settings = new QuerySqlBuilderSettings( );
            }

            // Optimise tree
            StructuredQuery optimisedQuery = StructuredQueryHelper.PruneQueryTree(query);

            // Generate SQL
            QueryBuilder queryBuilder = new QueryBuilder(optimisedQuery, settings);
            QueryBuild   result       = queryBuilder.GetSqlInternal( );

            // Logging
            using (MessageContext msg = new MessageContext("Reports"))
            {
                msg.Append(() => new string( '-', 50 ));
                msg.Append(() => "Final structured query:\n" + StructuredQueryHelper.ToXml(optimisedQuery));
                msg.Append(() => new string( '-', 50 ));
                msg.Append(() => "SQL:\n" + result.Sql);
                msg.Append(() => new string( '-', 50 ));
            }

            // Note: identify cache dependencies after getting SQL, so that any calculations are resolved into the structured query.
            IdentifyCacheDependencies(optimisedQuery, settings);

            return(result);
        }
Example #2
0
        public void Test_Append_TwoLevels()
        {
            const string testName  = "a";
            const string testLine1 = "Line 1";
            const string testLine2 = "Line 2";
            const string testLine3 = "Line 3";

            using (MessageContext outerMessageContext = new MessageContext(testName, MessageContextBehavior.Capturing))
            {
                outerMessageContext.Append(() => testLine1);
                Assert.That(outerMessageContext.GetMessage(), Is.EqualTo(testLine1),
                            "Incorrect out message after first append");
                using (MessageContext innerMessageContext = new MessageContext(testName, MessageContextBehavior.Capturing))
                {
                    innerMessageContext.Append(() => testLine2);
                    Assert.That(outerMessageContext.GetMessage(), Is.EqualTo(testLine1 + Environment.NewLine + MessageContext.Indent + testLine2),
                                "Incorrect outer message after second append");
                    Assert.That(innerMessageContext.GetMessage(), Is.EqualTo(testLine2),
                                "Incorrect inner message after second append");
                }

                Assert.That(outerMessageContext.GetMessage(), Is.EqualTo(testLine1 + Environment.NewLine + MessageContext.Indent + testLine2),
                            "Incorrect outer message after inner dispose");

                outerMessageContext.Append(() => testLine3);
                Assert.That(outerMessageContext.GetMessage(), Is.EqualTo(testLine1 + Environment.NewLine + MessageContext.Indent + testLine2 + Environment.NewLine + testLine3),
                            "Incorrect outer message after third append");
            }
        }
Example #3
0
        public void Test_Sample()
        {
            const string testName = "a";

            using (
                MessageContext messageContext1 = new MessageContext(testName,
                                                                    MessageContextBehavior.Capturing | MessageContextBehavior.New))
            {
                messageContext1.Append(() => "Start:");
                using (MessageContext messageContext2 = new MessageContext(testName))
                {
                    messageContext2.Append(() => "Addends:");
                    using (MessageContext messageContext3 = new MessageContext(testName))
                    {
                        messageContext3.Append(() => "Addend 1: 2");
                        messageContext3.Append(() => "Addend 2: 3");
                    }

                    messageContext2.Append(() => "Result:");
                    using (MessageContext messageContext4 = new MessageContext(testName))
                    {
                        messageContext4.Append(() => "Sum: 5");
                    }
                }

                Assert.That(messageContext1.GetMessage(),
                            Is.EqualTo(string.Format(
                                           @"Start:
{0}Addends:
{0}{0}Addend 1: 2
{0}{0}Addend 2: 3
{0}Result:
{0}{0}Sum: 5", MessageContext.Indent)));
            }
        }
Example #4
0
 private void LogMessage(CachingEntityAccessControlCheckerResult result)
 {
     // Force an indent by using two message contexts
     using (MessageContext outermessageContext = new MessageContext(EntityAccessControlService.MessageName))
     {
         outermessageContext.Append(() => string.Format("Cache '{0}' results:", CacheName));
         using (MessageContext innerMessageContext = new MessageContext(EntityAccessControlService.MessageName))
         {
             if (result.CacheResult.Count > 0)
             {
                 innerMessageContext.Append(() => string.Format(
                                                "Allowed: {0}",
                                                string.Join(", ",
                                                            result.CacheResult.Where(kvp => kvp.Value)
                                                            .Select(kvp => kvp.Key))));
                 innerMessageContext.Append(() => string.Format(
                                                "Denied: {0}",
                                                string.Join(", ",
                                                            result.CacheResult.Where(kvp => !kvp.Value)
                                                            .Select(kvp => kvp.Key))));
             }
             else
             {
                 innerMessageContext.Append(() => "No results found in the cache.");
             }
         }
     }
 }
        /// <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);
        }
 /// <summary>
 /// Record reason for not caching
 /// </summary>
 private static void LogReasonForNonCaching(string reason)
 {
     using (MessageContext msg = new MessageContext("Reports"))
     {
         msg.Append(() => "Query is uncacheable: " + reason);
     }
     EventLog.Application.WriteTrace("Query is uncacheable: " + reason);
 }
Example #7
0
        /// <summary>
        /// Convert a <see cref="Report" /> to a <see cref="StructuredQuery" />.
        /// </summary>
        /// <param name="report">The <see cref="Report" /> to convert. This cannot be null.</param>
        /// <param name="settings"></param>
        /// <returns>
        /// The converted report.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">report</exception>
        public StructuredQuery Convert(Report report, ReportToQueryConverterSettings settings)
        {
            if (report == null)
            {
                throw new ArgumentNullException("report");
            }
            if (settings == null)
            {
                settings = ReportToQueryConverterSettings.Default;
            }

            StructuredQuery result;
            CachingReportToQueryConverterValue cacheValue;

            CachingReportToQueryConverterKey key = new CachingReportToQueryConverterKey(report, settings);

            using (MessageContext msg = new MessageContext("Reports"))
            {
                // Check cache
                bool doConvert = !TryGetValue(key, msg, out cacheValue);

                // Check for force recalculation
                if (settings.RefreshCachedStructuredQuery)
                {
                    msg.Append(() => "CachingReportToQueryConverter refreshed forced");
                    doConvert = true;
                }

                if (doConvert)
                {
                    lock (_syncRoot)
                    {
                        using (CacheContext cacheContext = new CacheContext( ))
                        {
                            result     = Converter.Convert(report, settings);
                            cacheValue = new CachingReportToQueryConverterValue(result);

                            Cache.Add(key, cacheValue);

                            // Add the cache context entries to the appropriate CacheInvalidator
                            _cacheInvalidator.AddInvalidations(cacheContext, key);
                        }
                    }
                }
                else if (CacheContext.IsSet( ))
                {
                    // Add the already stored changes that should invalidate this cache
                    // entry to any outer or containing cache contexts.
                    using (CacheContext cacheContext = CacheContext.GetContext( ))
                    {
                        cacheContext.AddInvalidationsFor(_cacheInvalidator, key);
                    }
                }
            }

            result = cacheValue.StructuredQuery;
            return(result);
        }
Example #8
0
        public void Test_Append_SingleLevel()
        {
            const string testName  = "a";
            const string testLine1 = "Line 1";
            const string testLine2 = "Line 2";

            using (MessageContext messageContext = new MessageContext(testName, MessageContextBehavior.Capturing))
            {
                Assert.That(messageContext.GetMessage(), Is.Empty,
                            "Incorrect message before first append line");
                messageContext.Append(() => testLine1);
                Assert.That(messageContext.GetMessage(), Is.EqualTo(testLine1),
                            "Incorrect message after first append line");
                messageContext.Append(() => testLine2);
                Assert.That(messageContext.GetMessage(), Is.EqualTo(testLine1 + Environment.NewLine + testLine2),
                            "Incorrect message after second append line");
            }
        }
Example #9
0
        /// <summary>
        /// Create a human readable descriptio0n of the result of the security check.
        /// </summary>
        /// <param name="result"></param>
        /// <param name="messageContext"></param>
        /// <returns></returns>
        internal void WriteFooterMessage(IDictionary <long, bool> result, MessageContext messageContext)
        {
            if (messageContext == null)
            {
                throw new ArgumentNullException("messageContext");
            }

            messageContext.Append(() => "Result:");
            using (MessageContext innerMessageContext = new MessageContext(MessageName))
            {
                innerMessageContext.Append(() => string.Format("Allowed: {0}",
                                                               string.Join(", ", result.Where(kvp => kvp.Value)
                                                                           .Select(kvp => kvp.Key))));
                innerMessageContext.Append(() => string.Format("Denied: {0}",
                                                               string.Join(", ", result.Where(kvp => !kvp.Value)
                                                                           .Select(kvp => kvp.Key))));
            }
        }
Example #10
0
        /// <summary>
        /// Check the cache.
        /// Run the continuation callback if necessary.
        /// Apply presentation formatting.
        /// Store result in cache if possible.
        /// </summary>
        /// <typeparam name="T">Type of result after presentation formatting.</typeparam>
        /// <param name="reportCompletionData">The completion process for a partially processed report.</param>
        /// <param name="presentationCallback">Presentation formatting, such as conversion to webapi message.</param>
        /// <returns></returns>
        public string GetReportResult(ReportCompletionData reportCompletionData, Func <ServiceResult.ReportResult, string> presentationCallback)
        {
            if (reportCompletionData == null)
            {
                return(null);
            }

            ReportResultCacheKey cacheKey;
            string result;

            using (MessageContext msg = new MessageContext("Reports"))
            {
                cacheKey = reportCompletionData.ResultCacheKey;

                // A null cacheKey indicates that the report is uncacheable
                if (cacheKey == null)
                {
                    msg.Append(() => "ReportResultCache received no cache key");

                    // Invoke callback
                    ServiceResult.ReportResult serviceResult = reportCompletionData.PerformRun( );

                    // And format it
                    result = presentationCallback(serviceResult);
                }
                else
                {
                    // Check cache
                    bool fromCache = TryGetOrAdd(cacheKey, msg, out result, key1 =>
                    {
                        string formattedResult;

                        using (CacheContext cacheContext = new CacheContext( ))
                        {
                            // Call completion callback to run report
                            ServiceResult.ReportResult serviceResult = reportCompletionData.PerformRun( );

                            // Format result
                            formattedResult = presentationCallback(serviceResult);

                            // Add the cache context entries to the appropriate CacheInvalidator
                            _cacheInvalidator.AddInvalidations(cacheContext, cacheKey);

                            if (reportCompletionData.CacheContextDuringPreparation != null)
                            {
                                _cacheInvalidator.AddInvalidations(reportCompletionData.CacheContextDuringPreparation, cacheKey);
                            }
                        }
                        return(formattedResult);
                    });

                    // Note: No call to AddInvalidationsFor because no-one is listening.
                }
            }

            return(result);
        }
        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);
        }
Example #12
0
        /// <summary>
        /// Pre-load report entities.
        /// </summary>
        /// <param name="reportId">The ID of the report to preload.</param>
        public static void PreloadReport(EntityRef reportId)
        {
            using (MessageContext messageContext = new MessageContext("Reports"))
            {
                messageContext.Append(() => string.Format("Preload report {0}", reportId));

                var rq = new EntityRequest(reportId, ReportPreloaderQuery, "Preload report " + reportId.ToString( ));
                BulkPreloader.Preload(rq);
            }
        }
Example #13
0
        /// <summary>
        /// Try to get the value from cache, with logging.
        /// </summary>
        private bool TryGetOrAdd(CachingQueryRunnerKey key, MessageContext msg, out CachingQueryRunnerValue result, Func <CachingQueryRunnerKey, CachingQueryRunnerValue> valueFactory)
        {
            bool foundValue;

            foundValue = Cache.TryGetOrAdd(key, out result, valueFactory);

            msg.Append(() => "CachingQueryRunner key:" + key);
            if (foundValue)
            {
                var cacheValue = result;
                msg.Append(() => "CachingQueryRunner cache hit");
                msg.Append(() => $"Entry originally cached at {cacheValue.CacheTime}");
            }
            else
            {
                msg.Append(() => "CachingQueryRunner cache miss");
            }

            return(foundValue);
        }
Example #14
0
        /// <summary>
        /// Try to get the value from cache, with logging.
        /// </summary>
        private bool TryGetOrAdd(ReportResultCacheKey key, MessageContext msg, out string result, Func <ReportResultCacheKey, string> valueFactory)
        {
            bool foundValue;

            foundValue = Cache.TryGetOrAdd(key, out result, valueFactory);

            msg.Append(() => "ReportResultCache key:" + key.ToString( ));
            if (foundValue)
            {
                var cacheValue = result;
                msg.Append(() => "ReportResultCache cache hit");
                //msg.Append( ( ) => string.Format( "Entry originally cached at {0}", cacheValue.CacheTime ) );
            }
            else
            {
                msg.Append(() => "ReportResultCache cache miss");
            }

            return(foundValue);
        }
Example #15
0
        /// <summary>
        /// Try to get the value from cache, with logging.
        /// </summary>
        private bool TryGetValue(CachingReportToQueryConverterKey key, MessageContext msg, out CachingReportToQueryConverterValue result)
        {
            msg.Append(() => "CachingReportToQueryConverter key:" + key);

            CachingReportToQueryConverterValue cacheValue;
            bool foundValue;

            if (Cache.TryGetValue(key, out cacheValue))
            {
                msg.Append(() => "CachingReportToQueryConverter cache hit");
                msg.Append(() => $"Entry originally cached at {cacheValue.CacheTime}");
                result     = cacheValue;
                foundValue = true;
            }
            else
            {
                msg.Append(() => "CachingReportToQueryConverter cache miss");
                result     = null;
                foundValue = false;
            }

            return(foundValue);
        }
        /// <summary>
        /// Check whether the specified <paramref name="permission"/> exists between the <paramref name="subjectId"/>
        /// and the <paramref name="entities"/> using just a relationship. Used for create permission.
        /// </summary>
        /// <param name="subjectId">
        /// The ID of the subject (user or role).
        /// </param>
        /// <param name="permission">
        /// The permission (operation). This cannot be null.
        /// </param>
        /// <param name="entityType">
        /// The type of the checked entities.
        /// </param>
        /// <param name="entities">
        /// The checked entities. This cannot be null or contain null.
        /// </param>
        /// <param name="result">
        /// The map of entity IDs to whether the relationship exists.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// No argument can be null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="entities"/> cannot contain null.
        /// </exception>
        internal void CheckAccessControlByRelationship(long subjectId, EntityRef permission,
                                                       long entityType, IList <EntityRef> entities, IDictionary <long, bool> result)
        {
            if (permission == null)
            {
                throw new ArgumentNullException("permission");
            }
            if (entities == null)
            {
                throw new ArgumentNullException("entities");
            }
            if (entities.Contains(null))
            {
                throw new ArgumentException(@"Entities cannot contain null", "entities");
            }
            if (result == null)
            {
                throw new ArgumentNullException("result");
            }

            Subject            subject;
            IEnumerable <long> allowedEntityTypes;

            bool containType;

            using (new SecurityBypassContext())
            {
                subject            = Entity.Get <Subject>(new EntityRef(subjectId));
                allowedEntityTypes = AllowedEntityTypes(permission, subject);

                containType = allowedEntityTypes.Contains(entityType);
            }

            if (containType)
            {
                foreach (EntityRef entity in entities)
                {
                    result[entity.Id] = true;
                }

                using (MessageContext messageContext = new MessageContext(EntityAccessControlService.MessageName))
                {
                    messageContext.Append(() => string.Format(
                                              "'{0}' access to entities '{1}' allowed",
                                              Permissions.GetPermissionByAlias(permission),
                                              string.Join(", ", entities.Select(x => x.ToString()))));
                }
            }
        }
Example #17
0
 public void Append(StringBuilder sb)
 {
     if (Context == null)
     {
         sb.Append(Reason);
     }
     else
     {
         Context.Append(sb);
     }
     if (DependsOn != null)
     {
         sb.Append(" -> ");
         DependsOn.Append(sb);
     }
 }
        /// <summary>
        /// Write the entity member request structure to the security trace.
        /// </summary>
        /// <param name="entityMemberRequest">
        /// The <see cref="EntityMemberRequest"/> to write out the structure for. This cannot be null.
        /// </param>
        /// <param name="visited">
        /// (Optional) The set of already traced <see cref="EntityMemberRequest"/>s.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="entityMemberRequest"/> cannot be null.
        /// </exception>
        internal void TraceEntityMemberRequest(EntityMemberRequest entityMemberRequest, ISet <EntityMemberRequest> visited = null)
        {
            if (entityMemberRequest == null)
            {
                throw new ArgumentNullException("entityMemberRequest");
            }

            if (visited == null)
            {
                visited = new HashSet <EntityMemberRequest>();
            }
            visited.Add(entityMemberRequest);

            using (MessageContext messageContext = new MessageContext(EntityAccessControlService.MessageName))
            {
                foreach (RelationshipRequest relationshipRequest in entityMemberRequest.Relationships)
                {
                    // ReSharper disable AccessToForEachVariableInClosure
                    messageContext.Append(() =>
                    {
                        Relationship relationship;
                        EntityType target;
                        EntityType source;

                        relationship = Entity.Get <Relationship>(relationshipRequest.RelationshipTypeId.Id);
                        source       = relationshipRequest.IsReverse ? relationship.ToType : relationship.FromType;
                        target       = relationshipRequest.IsReverse ? relationship.FromType : relationship.ToType;
                        return(string.Format("-> from type '{0}' ({1}) via '{2}' ({3}) to type '{4}' ({5}){6}{7}",
                                             source.Name,
                                             source.Id,
                                             relationship.Name,
                                             relationship.Id,
                                             target.Name,
                                             target.Id,
                                             relationshipRequest.IsReverse ? " (Reverse) " : string.Empty,
                                             visited.Contains(relationshipRequest.RequestedMembers) ? " (Already listed, see above for relationships from this type)" : string.Empty));
                    });
                    // ReSharper restore AccessToForEachVariableInClosure
                    if (!visited.Contains(relationshipRequest.RequestedMembers))
                    {
                        TraceEntityMemberRequest(relationshipRequest.RequestedMembers, visited);
                    }
                }
            }
        }
Example #19
0
        public void Test_Append_NotCapturing()
        {
            const string testName1 = "a";
            const string testLine1 = "Line 1";
            bool         called;

            called = false;
            using (MessageContext messageContext = new MessageContext(testName1, MessageContextBehavior.Default))
            {
                messageContext.Append(() =>
                {
                    called = true;
                    return(testLine1);
                });
                Assert.That(called, Is.False, "Called");
                Assert.That(messageContext.GetMessage(), Is.Empty,
                            "Message is not empty");
            }
        }
Example #20
0
        /// <summary>
        /// Write out the diagnostic messages when a structured query execution fails.
        /// </summary>
        /// <param name="structuredQuery">
        /// The <see cref="StructuredQuery"/> that failed. This cannot be null.
        /// </param>
        /// <param name="messageContext">
        /// (Optional) The <see cref="MessageContext"/> to write the details to.
        /// </param>
        /// <param name="ex">
        /// The exception thrown when the query ran, or null if no exception was thrown.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="structuredQuery"/> cannot be null.
        /// </exception>
        public static void WriteInvalidSecurityReportMessage(StructuredQuery structuredQuery, MessageContext messageContext = null, Exception ex = null)
        {
            if (structuredQuery == null)
            {
                throw new ArgumentNullException("structuredQuery");
            }

            EventLog.Application.WriteWarning(
                "{0} ignored due to errors when running the report{1}",
                GetAccessRuleName(structuredQuery),
                ex == null ? string.Empty : ": " + ex.ToString());

            if (messageContext != null)
            {
                messageContext.Append(
                    () => string.Format(
                        "{0} ignored due to errors when running the report",
                        GetAccessRuleName(structuredQuery)));
            }
        }
Example #21
0
        public void Test_Append_OuterCapturingOnly()
        {
            const string testName1 = "a";
            const string testLine1 = "Line 1";
            const string testLine2 = "Line 2";

            using (MessageContext outerMessageContext = new MessageContext(testName1, MessageContextBehavior.Capturing))
            {
                outerMessageContext.Append(() => testLine1);
                Assert.That(outerMessageContext.GetMessage(), Is.EqualTo(testLine1),
                            "Incorrect outer message after first append");

                using (MessageContext innerMessageContext = new MessageContext(testName1))
                {
                    innerMessageContext.Append(() => testLine2);
                    Assert.That(innerMessageContext.GetMessage(), Is.Empty,
                                "Incorrect inner message after second append");
                    Assert.That(outerMessageContext.GetMessage(), Is.EqualTo(testLine1 + Environment.NewLine + MessageContext.Indent + testLine2),
                                "Incorrect outer message after second append");
                }
            }
        }
        /// <summary>
        /// Get the subjects for a user.
        /// </summary>
        /// <param name="user">
        /// The user to check. This cannot be null.
        /// </param>
        /// <returns>
        /// The subjects (i.e. user and roles).
        /// </returns>
        private ISet <long> GetSubjects(EntityRef user)
        {
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }

            ISet <long> subjectIds;

            subjectIds = new HashSet <long>(RoleRepository.GetUserRoles(user.Id));
            using (MessageContext messageContext = new MessageContext(EntityAccessControlService.MessageName))
            {
                messageContext.Append(() => string.Format(
                                          "Checking access for user '{0}' ({1}) in roles '{2}'",
                                          Entity.Get <UserAccount>(user).Name,
                                          user.Id,
                                          string.Join(", ", subjectIds.ToList().Select(id => string.Format("'{0}' ({1})", Entity.Get <Subject>(id).Name, id)))));
            }
            subjectIds.Add(user.Id);

            return(subjectIds);
        }
Example #23
0
        public void Test_NewContext()
        {
            const string testName1 = "a";
            const string testLine1 = "Line 1";
            const string testLine2 = "Line 2";

            using (MessageContext outerMessageContext = new MessageContext(testName1, MessageContextBehavior.Capturing))
            {
                outerMessageContext.Append(() => testLine1);
                Assert.That(outerMessageContext.GetMessage(), Is.EqualTo(testLine1),
                            "Incorrect outer message after first append");

                using (MessageContext innerMessageContext = new MessageContext(testName1, MessageContextBehavior.Capturing | MessageContextBehavior.New))
                {
                    innerMessageContext.Append(() => testLine2);
                    Assert.That(outerMessageContext.GetMessage(), Is.EqualTo(testLine1),
                                "Incorrect outer message after second append");
                    Assert.That(innerMessageContext.GetMessage(), Is.EqualTo(testLine2),
                                "Incorrect inner message after second append");
                }
            }
        }
Example #24
0
        /// <summary>
        /// Create a human readable description of the security check.
        /// </summary>
        /// <param name="entities"></param>
        /// <param name="permissions"></param>
        /// <param name="messageContext"></param>
        /// <returns></returns>
        internal void WriteHeaderMessage(IList <EntityRef> entities, IList <EntityRef> permissions, MessageContext messageContext)
        {
            if (messageContext == null)
            {
                throw new ArgumentNullException("messageContext");
            }

            messageContext.Append(
                () => string.Format(
                    "Access control check: Does user '{0}' have '{1}' access to entity(ies) '{2}'?",
                    RequestContext.GetContext().Identity.Name ?? "(null)",
                    permissions != null ? string.Join(", ", permissions.Select(Permissions.GetPermissionByAlias)) : "(null)",
                    entities != null ? string.Join(", ", entities) : "(null)"));

            // Problem: Need a way to get names without loading the entities. Otherwise, it creates an infinite loop.
            //messageContext.Append(
            //    () =>
            //    {
            //        IList<Resource> entitiesToCheck;

            //        using (new SecurityBypassContext())
            //        {
            //            entitiesToCheck = Entity.Get<Resource>(entities, new EntityRef("core:name")).ToList();
            //        }

            //        return string.Format(
            //            "Access control check: Does user '{0}' have '{1}' access to entity(ies) '{2}'?",
            //            RequestContext.GetContext().Identity.Name ?? "(null)",
            //            permissions != null
            //                ? string.Join(", ", permissions.Select(Permissions.GetPermissionAlias))
            //                : "(null)",
            //            entities != null
            //                ? string.Join(", ",
            //                    entitiesToCheck.Select(e => string.Format("'{0}' ({1})", e.Name, e.Id)))
            //                : "(null)");
            //    });
        }
        /// <summary>
        /// Get the structured query, possibly from cache.
        /// </summary>
        /// <param name="report">The report to convert.</param>
        /// <param name="settings">The report run settings.</param>
        /// <param name="suppressPreload">True if we should suppress preloading.</param>
        /// <returns>The structured query.</returns>
        private StructuredQuery GetStructuredQuery(Model.Report report, ReportSettings settings, bool suppressPreload)
        {
            using (MessageContext msg = new MessageContext("Reports"))
            {
                StructuredQuery immutableStructuredQuery;
                StructuredQuery structuredQuery;

                bool useStructuredQueryCache = settings.UseStructuredQueryCache;

                ReportToQueryConverterSettings converterSettings = new ReportToQueryConverterSettings
                {
                    SuppressPreload = suppressPreload,
                    RefreshCachedStructuredQuery = settings.RefreshCachedStructuredQuery,
                    SchemaOnly = settings.RequireSchemaMetadata
                };

                if (settings != null && settings.UseStructuredQueryCache)
                {
                    // don't allow mutations of cached copy
                    immutableStructuredQuery = CachedReportToQueryConverter.Convert(report, converterSettings);
                }
                else
                {
                    // don't allow mutations, just so we can log it correctly
                    immutableStructuredQuery = NonCachedReportToQueryConverter.Convert(report, converterSettings);
                }

                structuredQuery = immutableStructuredQuery.DeepCopy( ); // so we can mutate it (in case we need to)

                // Logging
                msg.Append(() => new String('-', 50));
                msg.Append(() => "GetStructuredQuery");
                msg.Append(() => "suppressPreload = " + suppressPreload);
                msg.Append(() => "useStructuredQueryCache = " + useStructuredQueryCache);
                msg.Append(() => "Structured Query:\n" + StructuredQueryHelper.ToXml(immutableStructuredQuery));
                msg.Append(() => new String('-', 50));

                return(structuredQuery);
            }
        }
        /// <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);
        }
Example #27
0
        /// <summary>
        /// Build the SQL, or collect it from cache.
        /// </summary>
        /// <param name="query"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        public QueryBuild BuildSql(StructuredQuery query, QuerySqlBuilderSettings settings)
        {
            // Validate
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }
            if (QuerySqlBuilder == null)
            {
                throw new InvalidOperationException("QuerySqlBuilder not set.");
            }
            if (settings == null)
            {
                settings = new QuerySqlBuilderSettings( );
            }

            // Check if query can even participate in cache
            if (!CachingQuerySqlBuilderKey.DoesRequestAllowForCaching(query, settings))
            {
                return(BuildSqlImpl(query, settings));
            }

            // Get a user-set key
            // (Users may share the same report SQL if they have the same set of read-rules)
            UserRuleSet userRuleSet = null;

            if (settings.RunAsUser != 0)
            {
                userRuleSet = UserRuleSetProvider.GetUserRuleSet(settings.RunAsUser, Permissions.Read);
                if (userRuleSet == null)
                {
                    throw new InvalidOperationException("Expected userRuleSet");   // Assert false
                }
            }

            // Create cache key
            CachingQuerySqlBuilderKey   key = new CachingQuerySqlBuilderKey(query, settings, userRuleSet);
            CachingQuerySqlBuilderValue cacheValue;

            using (MessageContext msg = new MessageContext("Reports"))
            {
                // Check for force recalculation
                if (settings.RefreshCachedSql)
                {
                    msg.Append(() => "CachingQuerySqlBuilder refreshed forced");
                    _cacheInvalidator.DebugInvalidations.Add(key);
                    Cache.Remove(key);
                }

                // In some circumstances, a result will be uncacheable, so we just return 'null' in the delegate instead.
                // However, on the first access, we will still be doing the calculation, so store it here.
                CachingQuerySqlBuilderValue calculatedOnThisAccess = null;

                // Check cache
                bool fromCache = TryGetOrAdd(key, msg, out cacheValue, callbackKey =>
                {
                    // This callback is called if we have a cache miss

                    using (CacheContext cacheContext = new CacheContext( ))
                    {
                        QueryBuild queryResult = BuildSqlImpl(query, settings);
                        cacheValue             = new CachingQuerySqlBuilderValue(query, queryResult);
                        calculatedOnThisAccess = cacheValue;

                        if (queryResult.SqlIsUncacheable)
                        {
                            return(null);
                        }
                        else
                        {
                            // Add the cache context entries to the appropriate CacheInvalidator
                            _cacheInvalidator.AddInvalidations(cacheContext, callbackKey);
                            return(cacheValue);
                        }
                    }
                });

                // cacheValue will be null if the result was uncacheable
                if (cacheValue == null)
                {
                    if (calculatedOnThisAccess != null)
                    {
                        // In this path, the result was uncacheable, so the cache returned a 'null',
                        // but it was the initial calculation run anyway, so we can get the value from callbackValue.
                        cacheValue = calculatedOnThisAccess;
                    }
                    else
                    {
                        // In this path, the result was uncacheable, but someone had asked previously, and stored
                        // the null, so we need to actually build the SQL again for this scenario.
                        // Note: don't need to do anything with cache context, because this cache is not participating.
                        // And if there's a parent context set, then the call to BuildSqlImpl will just talk directly to that context.
                        QueryBuild queryResult = BuildSqlImpl(query, settings);
                        cacheValue = new CachingQuerySqlBuilderValue(query, queryResult);
                    }
                }
                else if (fromCache && CacheContext.IsSet( ))
                {
                    // Add the already stored changes that should invalidate this cache
                    // entry to any outer or containing cache contexts.
                    using (CacheContext cacheContext = CacheContext.GetContext( ))
                    {
                        cacheContext.AddInvalidationsFor(_cacheInvalidator, key);
                    }
                }
            }

            if (cacheValue == null)
            {
                throw new Exception("Assert false");
            }

            // Mutate returned result to be suitable for current query
            QueryBuild result;

            if (cacheValue.OriginalQuery == query)
            {
                result = cacheValue.QueryResult;
            }
            else
            {
                result = MutateResultToMatchCurrentQuery(cacheValue, query);
            }

            return(result);
        }
        /// <summary>
        /// Check whether the user has all the specified
        /// <paramref name="permissions">access</paramref> to the specified <paramref name="entities"/>.
        /// </summary>
        /// <param name="entities">
        ///     The entities to check. This cannot be null or contain null.
        /// </param>
        /// <param name="permissions">
        ///     The permissions or operations to check. This cannot be null or contain null.
        /// </param>
        /// <param name="user">
        ///     The user requesting access. This cannot be null.
        /// </param>
        /// <returns>
        /// A mapping of each entity ID to whether the user has access (true) or not (false).
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// No argument can be null.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// Neither <paramref name="entities"/> nor <paramref name="permissions"/> can contain null.
        /// </exception>
        public IDictionary <long, bool> CheckAccess(IList <EntityRef> entities, IList <EntityRef> permissions, EntityRef user)
        {
            if (entities == null)
            {
                throw new ArgumentNullException("entities");
            }
            if (permissions == null)
            {
                throw new ArgumentNullException("permissions");
            }
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }

            IDictionary <long, bool> result;
            ISet <long> subjects;
            IDictionary <long, ISet <EntityRef> >        entityTypes;
            Dictionary <long, IDictionary <long, bool> > permissionToAccess;
            // Dictionary keyed off report id to entities
            Dictionary <long, ISet <long> > queryResultsCache;
            ISet <long> allEntities;

            if (SkipCheck(user))
            {
                result = SetAll(entities.Select(e => e.Id), true);
            }
            else if (entities.Count == 0)
            {
                result = new Dictionary <long, bool>();
            }
            else
            {
                using (new SecurityBypassContext())
                {
                    subjects = GetSubjects(user);

                    using (MessageContext messageContext = new MessageContext(EntityAccessControlService.MessageName))
                    {
                        messageContext.Append(() => "Checking access rules:");

                        entityTypes        = null;
                        permissionToAccess = new Dictionary <long, IDictionary <long, bool> >();
                        queryResultsCache  = new Dictionary <long, ISet <long> >();
                        allEntities        = null;

                        foreach (EntityRef permission in permissions)
                        {
                            permissionToAccess[permission.Id] = SetAll(entities.Select(e => e.Id), false);

                            if (allEntities == null &&
                                !permission.Equals(Permissions.Create))
                            {
                                // Add all the entities to a set
                                allEntities = new HashSet <long>(entities.Select(e => e.Id));
                            }

                            foreach (long subject in subjects)
                            {
                                if (entityTypes == null)
                                {
                                    entityTypes = EntityTypeRepository.GetEntityTypes(entities);
                                }

                                bool accessGrantedToAll = false;

                                // entityType maps a sorted list of type ids to instance entity Refs
                                foreach (KeyValuePair <long, ISet <EntityRef> > entityType in entityTypes)
                                {
                                    using (MessageContext perTypeMessageContext = new MessageContext(EntityAccessControlService.MessageName))
                                    {
                                        perTypeMessageContext.Append(() =>
                                                                     ConstructCheckMessage(entities, subjects, entityTypes, subject, entityType));

                                        using (new MessageContext(EntityAccessControlService.MessageName))
                                        {
                                            // Automatically allow read access (only) to fields, relationships and types
                                            CheckAutomaticAccess(permission, entityType, permissionToAccess);

                                            if (EntityRefComparer.Instance.Equals(permission, Permissions.Create))
                                            {
                                                CheckAccessControlByRelationship(
                                                    subject,
                                                    permission,
                                                    entityType.Key,
                                                    entityType.Value.ToList(),
                                                    permissionToAccess[permission.Id]);
                                            }
                                            else
                                            {
                                                CheckAccessControlByQuery(
                                                    subject,
                                                    permission,
                                                    entityType.Key,
                                                    entityType.Value.ToList(),
                                                    allEntities, queryResultsCache,
                                                    permissionToAccess[permission.Id]);
                                            }

                                            // Skip remaining checks if access is granted to all requested entities
                                            // for the current permission.
                                            if (permissionToAccess[permission.Id].All(kvp => kvp.Value))
                                            {
                                                messageContext.Append(
                                                    () => "Access granted to all entities. Not checking additional access rules.");
                                                accessGrantedToAll = true;
                                                break;
                                            }
                                        }
                                    }
                                }

                                // Skip remaining checks if access is granted to all requested entities
                                // for the current permission.
                                if (accessGrantedToAll || permissionToAccess[permission.Id].All(kvp => kvp.Value))
                                {
                                    if (!accessGrantedToAll)
                                    {
                                        messageContext.Append(() => "Access granted to all entities. Not checking additional access rules.");
                                    }
                                    break;
                                }
                            }
                        }

                        result = CollateAccess(entities, permissionToAccess);
                        result = AllowAccessToTypelessIds(result, entityTypes);

                        // Add all the containing roles to the cache so a role change invalidates this
                        // user's security cache entries.
                        using (CacheContext cacheContext = CacheContext.GetContext())
                        {
                            cacheContext.Entities.Add(subjects);
                        }
                    }
                }
            }

            return(result);
        }
        /// <summary>
        /// Check whether the queries for the specified <paramref name="permission"/> allow the <paramref name="subjectId"/>
        /// access to <paramref name="entities"/> using the related security queries. Used for read, modify and delete
        /// permissions.
        /// </summary>
        /// <param name="subjectId">
        /// The ID of the subject (user or role).
        /// </param>
        /// <param name="permission">
        /// The permission (operation). This cannot be null.
        /// </param>
        /// <param name="entityType">
        /// The type ID of the entities to check. This cannot be null.
        /// </param>
        /// <param name="entities">
        /// The entities to check. This cannot be null or contain null.
        /// </param>
        /// <param name="allEntities">
        /// All entities.
        /// </param>
        /// <param name="queryResultsCache">
        /// The query results cache.
        /// </param>
        /// <param name="result">
        /// The map of entity IDs to whether the relationship exists.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// No argument can be null.
        /// </exception>
        internal void CheckAccessControlByQuery(long subjectId, EntityRef permission,
                                                long entityType, IList <EntityRef> entities, ISet <long> allEntities,
                                                IDictionary <long, ISet <long> > queryResultsCache, IDictionary <long, bool> result)
        {
            using (Profiler.MeasureAndSuppress("CheckAccessControlByQuery"))
            {
                if (permission == null)
                {
                    throw new ArgumentNullException("permission");
                }
                if (result == null)
                {
                    throw new ArgumentNullException("result");
                }
                if (entities == null)
                {
                    throw new ArgumentNullException("entities");
                }
                if (allEntities == null)
                {
                    throw new ArgumentNullException("allEntities");
                }
                if (queryResultsCache == null)
                {
                    throw new ArgumentNullException("queryResultsCache");
                }
                if (entities.Contains(null))
                {
                    throw new ArgumentException("Cannot check access for null entities", "entities");
                }

                IEnumerable <AccessRuleQuery> queries;

                // Allow access to temporary IDs
                if (AllowAccessToTemporaryIds(result))
                {
                    return;
                }

                using (MessageContext messageContext = new MessageContext(EntityAccessControlService.MessageName))
                {
                    queries = QueryRepository.GetQueries(subjectId, permission, new[] { entityType });
                    QueryResult queryResult;

                    // Check if any queries grant access to all instances of the type
                    StructuredQuery shortCircuitQuery = CheckIfAnyQueryProvideAccessToAllInstancesOfType(entityType, queries, entities, result);
                    if (shortCircuitQuery != null)
                    {
                        messageContext.Append(
                            () => string.Format(
                                "{0} allowed '{1}' access to entities '{2}' because it allows access to all instances of the type.",
                                AccessControlDiagnosticsHelper.GetAccessRuleName(shortCircuitQuery),
                                Permissions.GetPermissionByAlias(permission),
                                string.Join(", ", entities.Select(x => x.ToString()))));
                        return;
                    }

                    long securityOwnerRelId = WellKnownAliases.CurrentTenant.SecurityOwner;

                    foreach (AccessRuleQuery accessRuleQuery in queries)
                    {
                        StructuredQuery structuredQuery = accessRuleQuery.Query;
                        var             allowedEntities = new HashSet <long>();
                        ISet <long>     queryResultSet;

                        if (!queryResultsCache.TryGetValue(accessRuleQuery.ReportId, out queryResultSet))
                        {
                            var querySettings = new QuerySettings
                            {
                                SecureQuery = false,
                                Hint        = "security - " + Name
                            };

                            bool filtered = false;

                            if (allEntities.Count <= MaximumNumberOfFilteredEntities)
                            {
                                filtered = true;
                                querySettings.SupportRootIdFilter = true;
                                querySettings.RootIdFilterList    = allEntities;
                            }

                            queryResult = null;
                            try
                            {
                                using (MessageContext msg = new MessageContext("Reports", MessageContextBehavior.New))
                                {
                                    queryResult = Factory.QueryRunner.ExecuteQuery(structuredQuery, querySettings);
                                }
                            }
                            catch (Exception ex)
                            {
                                AccessControlDiagnosticsHelper.WriteInvalidSecurityReportMessage(structuredQuery, messageContext, ex);
                            }

                            queryResultSet = new HashSet <long>();

                            if (queryResult != null && QueryInspector.IsQueryUndamaged(structuredQuery))
                            {
                                foreach (DataRow dataRow in queryResult.DataTable.Rows)
                                {
                                    var id = dataRow.Field <long>(0);
                                    if (filtered || allEntities.Contains(id))
                                    {
                                        queryResultSet.Add(id);
                                    }
                                }
                            }
                            else
                            {
                                if (queryResult != null)
                                {
                                    AccessControlDiagnosticsHelper.WriteInvalidSecurityReportMessage(structuredQuery, messageContext);
                                }
                            }

                            queryResultsCache[accessRuleQuery.ReportId] = queryResultSet;
                        }

                        foreach (EntityRef entityRef in entities)
                        {
                            if (queryResultSet.Contains(entityRef.Id) &&
                                result.ContainsKey(entityRef.Id))
                            {
                                allowedEntities.Add(entityRef.Id);
                                result[entityRef.Id] = true;
                            }
                        }

                        // ReSharper disable AccessToForEachVariableInClosure
                        // ReSharper disable SpecifyACultureInStringConversionExplicitly
                        if (allowedEntities.Count > 0)
                        {
                            messageContext.Append(
                                () => string.Format(
                                    "{0} allowed '{1}' access to entities '{2}' out of '{3}'",
                                    AccessControlDiagnosticsHelper.GetAccessRuleName(structuredQuery),
                                    Permissions.GetPermissionByAlias(permission),
                                    string.Join(", ",
                                                allowedEntities.Select(x => x.ToString())),
                                    string.Join(", ", entities.Select(x => x.ToString()))));
                        }
                        else
                        {
                            messageContext.Append(
                                () => string.Format(
                                    "{0} returned no results for '{1}' access to entities '{2}'",
                                    AccessControlDiagnosticsHelper.GetAccessRuleName(structuredQuery),
                                    Permissions.GetPermissionByAlias(permission),
                                    string.Join(", ", entities.Select(x => x.ToString()))));
                        }
                        // ReSharper restore AccessToForEachVariableInClosure
                        // ReSharper restore SpecifyACultureInStringConversionExplicitly

                        // Set the cache invalidation information
                        using (CacheContext cacheContext = CacheContext.GetContext())
                        {
                            // ******************* TEMPORARY WORKAROUND ***********************
                            // Until we properly implement filtering the invalidating relationships and fields by type
                            // we will ignore invalidating on the security owner relationship

                            cacheContext.RelationshipTypes.Add(
                                StructuredQueryHelper.GetReferencedRelationships(structuredQuery).Where(er => er.Id != securityOwnerRelId).Select(er => er.Id));
                            cacheContext.FieldTypes.Add(
                                StructuredQueryHelper.GetReferencedFields(structuredQuery, true, true).Select(er => er.Id));
                        }
                    }
                }
            }
        }
Example #30
0
        /// <summary>
        /// Build the SQL, or collect it from cache.
        /// </summary>
        /// <param name="query"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        public QueryResult ExecuteQuery(StructuredQuery query, QuerySettings settings)
        {
            // Validate
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }
            if (QueryRunner == null)
            {
                throw new InvalidOperationException("QueryRunner not set.");
            }
            if (settings == null)
            {
                settings = new QuerySettings( );
            }

            // Determine if we should cache .. and the cache key
            QueryBuild            builtQuery;
            CachingQueryRunnerKey key;
            CacheContext          queryBuilderCacheContext;

            using (queryBuilderCacheContext = new CacheContext())
            {
                key = CreateCacheKeyAndQuery(query, settings, out builtQuery);
            }

            // A null key means that the ersult should not participate in caching
            if (key == null)
            {
                return(RunQueryImpl(query, settings, builtQuery));
            }

            CachingQueryRunnerValue cacheValue;

            using (MessageContext msg = new MessageContext("Reports"))
            {
                // Check for force recalculation
                if (settings.RefreshCachedResult)
                {
                    msg.Append(() => "CachingQueryRunner refreshed forced");
                    Cache.Remove(key);
                }

                // Run query
                bool fromCache = TryGetOrAdd(key, msg, out cacheValue, callbackKey =>
                {
                    using (CacheContext cacheContext = new CacheContext( ))
                    {
                        QueryResult queryResult = RunQueryImpl(query, settings, builtQuery);
                        cacheValue = new CachingQueryRunnerValue(query, queryResult);

                        // Add the cache context entries to the appropriate CacheInvalidator
                        _cacheInvalidator.AddInvalidations(cacheContext, callbackKey);
                        _cacheInvalidator.AddInvalidations(queryBuilderCacheContext, callbackKey);
                    }

                    return(cacheValue);
                });

                if (fromCache && CacheContext.IsSet())
                {
                    using (CacheContext cacheContext = CacheContext.GetContext( ))
                    {
                        // Add the already stored changes that should invalidate this cache
                        // entry to any outer or containing cache contexts.
                        cacheContext.AddInvalidationsFor(_cacheInvalidator, key);
                    }
                }
            }

            if (cacheValue == null)
            {
                throw new Exception("Assert false");
            }

            // Mutate returned result to be suitable for current query
            QueryResult result;

            if (cacheValue.OriginalQuery == query)
            {
                result = cacheValue.QueryResult;
            }
            else
            {
                result = MutateResultToMatchCurrentQuery(cacheValue, query);
            }

            return(result);
        }