/// <summary>
        /// Is there an access rule for the specified type(s) that includes the requested permission? E.g. create.
        /// </summary>
        /// <param name="entityTypes">The <see cref="EntityType"/>s to check. This cannot be null or contain null.</param>
        /// <param name="permission">The permission being sought.</param>
        /// <param name="user"> The user requesting access. This cannot be null. </param>
        /// <returns>
        /// A mapping the entity type ID to whether the user can create instances of that
        /// type (true) or not (false).
        /// </returns>
        public IDictionary <long, bool> CheckTypeAccess(IList <EntityType> entityTypes, EntityRef permission, EntityRef user)
        {
            if (entityTypes == null)
            {
                throw new ArgumentNullException(nameof(entityTypes));
            }
            if (permission == null)
            {
                throw new ArgumentNullException(nameof(permission));
            }
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            IDictionary <long, bool> result;
            Stopwatch stopwatch;

            stopwatch = new Stopwatch();
            try
            {
                stopwatch.Start();

                result = Checker.CheckTypeAccess(entityTypes, permission, user);

                if (!EntityAccessControlChecker.SkipCheck(user))
                {
                    // Update counters for permission and the total
                    AccessControlPermissionCounters.GetPerformanceCounter <RatePerSecond32PerformanceCounter>(
                        AccessControlPermissionPerformanceCounters.RateCounterName,
                        permission.Alias).Increment();
                    AccessControlPermissionCounters.GetPerformanceCounter <NumberOfItems64PerformanceCounter>(
                        AccessControlPermissionPerformanceCounters.CountCounterName,
                        permission.Alias).Increment();

                    // Update single instance counters
                    AccessControlCounters.GetPerformanceCounter <RatePerSecond32PerformanceCounter>(
                        AccessControlPerformanceCounters.CheckRateCounterName).Increment();
                    AccessControlCounters.GetPerformanceCounter <NumberOfItems64PerformanceCounter>(
                        AccessControlPerformanceCounters.CheckCountCounterName).Increment();
                }
            }
            finally
            {
                // Update the average timer counter access control checks
                stopwatch.Stop();

                if (!EntityAccessControlChecker.SkipCheck(user))
                {
                    AccessControlCounters.GetPerformanceCounter <AverageTimer32PerformanceCounter>(
                        AccessControlPerformanceCounters.CheckDurationCounterName).AddTiming(stopwatch);
                }
            }

            return(result);
        }
        /// <summary>
        /// Log the access control check before passing it to <see cref="Checker"/>.
        /// </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;
            Stopwatch stopwatch = null;

            using (EntryPointContext.AppendEntryPoint("AccessControl"))
            {
                try
                {
                    stopwatch = Stopwatch.StartNew( );
                    result    = Checker.CheckAccess(entities, permissions, user);
                }
                finally
                {
                    if (stopwatch != null)
                    {
                        stopwatch.Stop( );
                    }
                }
            }

            if (!EntityAccessControlChecker.SkipCheck(user))
            {
                Dictionary <long, bool> cacheResult = null;
                var cachingResult = result as ICachingEntityAccessControlCheckerResult;
                if (cachingResult != null)
                {
                    cacheResult = cachingResult.CacheResult;
                }

                Trace.TraceCheckAccess(result, permissions, user, cacheResult, stopwatch.ElapsedMilliseconds);
            }

            return(result);
        }
        /// <summary>
        /// Is there an access rule for the specified type(s) that includes the requested permission? E.g. create.
        /// </summary>
        /// <param name="entityTypes">The <see cref="EntityType"/>s to check. This cannot be null or contain null.</param>
        /// <param name="permission">The permission being sought.</param>
        /// <param name="user"> The user requesting access. This cannot be null. </param>
        /// <returns>
        /// A mapping the entity type ID to whether the user can create instances of that
        /// type (true) or not (false).
        /// </returns>
        public IDictionary <long, bool> CheckTypeAccess(IList <EntityType> entityTypes, EntityRef permission, EntityRef user)
        {
            if (entityTypes == null)
            {
                throw new ArgumentNullException(nameof(entityTypes));
            }
            if (permission == null)
            {
                throw new ArgumentNullException(nameof(permission));
            }
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            IDictionary <long, bool> result;
            Stopwatch stopwatch = null;

            using (EntryPointContext.AppendEntryPoint("AccessControl"))
            {
                try
                {
                    stopwatch = Stopwatch.StartNew( );
                    result    = Checker.CheckTypeAccess(entityTypes, permission, user);
                }
                finally
                {
                    stopwatch?.Stop( );
                }
            }

            if (!EntityAccessControlChecker.SkipCheck(user))
            {
                Trace.TraceCheckTypeAccess(
                    result,
                    permission,
                    user,
                    entityTypes.Select(et => new EntityRef(et)).ToList(),
                    stopwatch.ElapsedMilliseconds);
            }

            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)
        {
            IDictionary <long, bool> results;

            if (entities == null)
            {
                throw new ArgumentNullException(nameof(entities));
            }

            if (permissions == null)
            {
                throw new ArgumentNullException(nameof(permissions));
            }

            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            if (EntityAccessControlChecker.SkipCheck(user))
            {
                results = entities.Select(e => e.Id).ToDictionarySafe(x => x, x => true);
            }
            else if (entities.Count == 0)
            {
                results = new Dictionary <long, bool>();
            }
            else
            {
                results = new Dictionary <long, bool>();

                var checkerResultsList = _entityAccessControlCheckers.Select(checker => checker.CheckAccess(entities, permissions, user)).ToList();

                foreach (var e in entities)
                {
                    var id = e.Id;
                    results[id] = checkerResultsList.All(checkerResults => HaveAccess(checkerResults, id));
                }
            }

            return(results);
        }
예제 #5
0
        /// <summary>
        /// Re-used cache checking logic that can be used for caching eithe instances lookups or type lookups.
        /// </summary>
        /// <param name="entities">The entities/types 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>
        /// <param name="entityIdCallback">Callback used to determine how to get the ID of TEntity.</param>
        /// <param name="innerCheckAccessCallback">Callback used to perform the uncached check.</param>
        /// <returns>A mapping of each entity ID to whether the user has access (true) or not (false).</returns>
        private IDictionary <long, bool> CachedCheckImpl <TEntity>(IList <TEntity> entities, IList <EntityRef> permissions, EntityRef user,
                                                                   Func <TEntity, long> entityIdCallback,
                                                                   Func <IList <TEntity>, IList <EntityRef>, EntityRef, IDictionary <long, bool> > innerCheckAccessCallback)
        {
            TKey cacheKey;
            CachingEntityAccessControlCheckerResult result;
            IList <TEntity> toCheckEntities;

            long[] permissionIdArray;
            IDictionary <long, bool> innerResult;

            // If SecurityBypassContext is active, avoid the cache. Otherwise,
            // the cache will remember the user had access to entities that
            // they may not have.
            if (EntityAccessControlChecker.SkipCheck(user))
            {
                innerResult = innerCheckAccessCallback(entities, permissions, user);
                return(innerResult);
            }

            result            = new CachingEntityAccessControlCheckerResult();
            toCheckEntities   = null;
            permissionIdArray = permissions.Select(x => x.Id).ToArray();


            // Determine uncached entities
            using (CacheContext cacheContext = CacheContext.IsSet( ) ? CacheContext.GetContext( ) : null)
            {
                foreach (TEntity entity in entities)
                {
                    long entityId = entityIdCallback(entity);

                    cacheKey = CreateKey(user.Id, entityId, permissionIdArray);
                    bool cacheEntry;

                    if (Cache.TryGetValue(cacheKey, out cacheEntry))
                    {
                        result.CacheResult[entityId] = cacheEntry;

                        // Add the already stored changes that should invalidate this cache
                        // entry to any outer or containing cache contexts.
                        if (cacheContext != null)
                        {
                            cacheContext.AddInvalidationsFor(_cacheInvalidator, cacheKey);
                        }
                    }
                    else
                    {
                        if (toCheckEntities == null)
                        {
                            toCheckEntities = new List <TEntity>();
                        }
                        toCheckEntities.Add(entity);
                    }
                }
            }

            LogMessage(result);

            if (toCheckEntities != null)
            {
                using (CacheContext cacheContext = new CacheContext( ))
                {
                    innerResult = innerCheckAccessCallback(toCheckEntities, permissions, user);

                    foreach (KeyValuePair <long, bool> entry in innerResult)
                    {
                        long entityId      = entry.Key;
                        bool accessGranted = entry.Value;

                        result.Add(entityId, accessGranted);

                        // Cache the results
                        cacheKey         = CreateKey(user.Id, entry.Key, permissionIdArray);
                        Cache [cacheKey] = accessGranted;

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

            // Add the results from the originally cached entities
            foreach (KeyValuePair <long, bool> entry in result.CacheResult)
            {
                result[entry.Key] = entry.Value;
            }

            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;

            var stopwatch = new Stopwatch();

            try
            {
                stopwatch.Start();

                result = Checker.CheckAccess(entities, permissions, user);

                if (!EntityAccessControlChecker.SkipCheck(user))
                {
                    // Update counters for each permission and the total
                    foreach (IEntityRef entityRef in permissions)
                    {
                        AccessControlPermissionCounters.GetPerformanceCounter <RatePerSecond32PerformanceCounter>(
                            AccessControlPermissionPerformanceCounters.RateCounterName,
                            entityRef.Alias ?? entityRef.Id.ToString(CultureInfo.InvariantCulture)).Increment();
                        AccessControlPermissionCounters.GetPerformanceCounter <NumberOfItems64PerformanceCounter>(
                            AccessControlPermissionPerformanceCounters.CountCounterName,
                            entityRef.Alias ?? entityRef.Id.ToString(CultureInfo.InvariantCulture)).Increment();
                    }

                    // Update single instance counters
                    AccessControlCounters.GetPerformanceCounter <RatePerSecond32PerformanceCounter>(
                        AccessControlPerformanceCounters.CheckRateCounterName).Increment();
                    AccessControlCounters.GetPerformanceCounter <NumberOfItems64PerformanceCounter>(
                        AccessControlPerformanceCounters.CheckCountCounterName).Increment();

                    // Update cache results
                    var cachingResult = result as ICachingEntityAccessControlCheckerResult;
                    if (cachingResult != null)
                    {
                        AccessControlCounters.GetPerformanceCounter <PercentageRatePerformanceCounter>(
                            AccessControlPerformanceCounters.CacheHitPercentageCounterName)
                        .AddHits(cachingResult.CacheResult.Keys.Count());
                        AccessControlCounters.GetPerformanceCounter <PercentageRatePerformanceCounter>(
                            AccessControlPerformanceCounters.CacheHitPercentageCounterName)
                        .AddMisses(result.Keys.Count() - cachingResult.CacheResult.Keys.Count());
                    }
                }
            }
            finally
            {
                // Update the average timer counter access control checks
                stopwatch.Stop();

                if (!EntityAccessControlChecker.SkipCheck(user))
                {
                    AccessControlCounters.GetPerformanceCounter <AverageTimer32PerformanceCounter>(
                        AccessControlPerformanceCounters.CheckDurationCounterName).AddTiming(stopwatch);
                }
            }

            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(nameof(entities));
            }
            if (permissions == null)
            {
                throw new ArgumentNullException(nameof(permissions));
            }
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            IDictionary <long, bool> result;
            IList <EntityRef>        relationshipTypeCheckPermissions;
            IList <EntityRef>        relationshipCheckPermissions;
            ISet <EntityRef>         entitiesToCheck;
            ISet <EntityRef>         accessibleEntities;

            using (MessageContext messageContext = new MessageContext(EntityAccessControlService.MessageName))
            {
                messageContext.Append(() => "Checking access rules first:");
                result = Checker.CheckAccess(entities, permissions, user);
            }
            entitiesToCheck = result
                              .Where(kvp => !kvp.Value)
                              .Select(kvp => new EntityRef(kvp.Key))
                              .ToSet();

            if (!EntityAccessControlChecker.SkipCheck(user) && entitiesToCheck.Count > 0 && permissions.Count > 0)
            {
                using (MessageContext messageContext = new MessageContext(EntityAccessControlService.MessageName))
                    using (new SecurityBypassContext())
                    {
                        messageContext.Append(() => string.Format(
                                                  "Checking for 'secures 'to' type' and 'secures 'from' type' flags for entities '{0}'",
                                                  string.Join(", ", entities.Select(x => x.ToString()))));

                        relationshipTypeCheckPermissions =
                            permissions.Where(p => EntityRefComparer.Instance.Equals(p, Permissions.Create))
                            .ToList();
                        relationshipCheckPermissions =
                            permissions.Where(p => !EntityRefComparer.Instance.Equals(p, Permissions.Create))
                            .ToList();

                        accessibleEntities = new HashSet <EntityRef>(entitiesToCheck);

                        // Create relies on the relationship type. Currently assumes Create is the only permission
                        // checked by relationship type.
                        if (relationshipTypeCheckPermissions.Count > 0)
                        {
                            accessibleEntities =
                                CheckAccessControlByRelationshipType(
                                    relationshipTypeCheckPermissions.Single(),
                                    user,
                                    accessibleEntities);
                        }

                        // Other permissions rely on the relationship existing
                        if ((relationshipCheckPermissions.Count > 0) && (accessibleEntities.Count > 0))
                        {
                            accessibleEntities =
                                CheckAccessControlByRelationship(relationshipCheckPermissions, user, accessibleEntities);
                        }

                        // Tell the user if there is nothing to check.
                        using (MessageContext innerMessageContext = new MessageContext(EntityAccessControlService.MessageName))
                        {
                            if (relationshipTypeCheckPermissions.Count == 0 &&
                                (relationshipCheckPermissions.Count == 0 && accessibleEntities.Count > 0))
                            {
                                innerMessageContext.Append(() => "No relationships to check.");
                            }
                            else
                            {
                                if (accessibleEntities.Count == 0)
                                {
                                    innerMessageContext.Append(() => "Relationships did not grant additional access.");
                                }
                                else
                                {
                                    innerMessageContext.Append(() => string.Format("Relationships granted access to entities '{0}'",
                                                                                   string.Join(", ", accessibleEntities.Select(er => er.Id))));
                                }
                            }
                        }

                        // Set the accessible entities
                        foreach (EntityRef entityRef in accessibleEntities)
                        {
                            result[entityRef.Id] = true;
                        }
                    }
            }

            return(result);
        }