/// <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); }
/// <summary> /// Determines whether the specified access is granted to the type. /// For creates this means the user can create new instances of the type. /// For other permissions, it means there is at least one applicable access rule that may grant that permission to instances of that type. /// This IS NOT the API to use if you want to see if the type itself can be modified. Use Check or Demand instead. /// </summary> /// <param name="entityTypes">The entity types to check. This cannot be null.</param> /// <param name="permission">The permissions to check for. This cannot be null.</param> /// <returns> /// A mapping of the entity ID to a flag. The flag is true if /// the user has access, false if not. /// </returns> /// <exception cref="InvalidOperationException"> /// <see cref="RequestContext"/> must be set. /// </exception> public IDictionary <long, bool> CheckAccessRuleApplesToType(IList <EntityType> entityTypes, EntityRef permission) { if (entityTypes == null) { throw new ArgumentNullException("entityTypes"); } if (permission == null) { throw new ArgumentNullException("permission"); } if (!RequestContext.IsSet) { throw new InvalidOperationException("RequestContext not set"); } if (SkipCheck( )) { return(entityTypes.ToDictionarySafe(x => x.Id, x => true)); } IDictionary <long, bool> result; using (MessageContext messageContext = new MessageContext(MessageName, GetBehavior(entityTypes.Select(e => e.Id)))) { WriteHeaderMessage( entityTypes.Select(et => new EntityRef(et)).ToList(), new [] { Permissions.Create }, messageContext); EntityRef user = RequestContext.GetContext( ).Identity.Id; result = EntityAccessControlChecker.CheckTypeAccess(entityTypes, Permissions.Create, user); WriteFooterMessage(result, messageContext); if (ShouldWriteSecurityTraceMessage(result)) { WriteSecurityTraceMessage(messageContext); } } return(result); }
/// <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 wither the user has access to the specified entity. /// </summary> /// <param name="entities"> /// The entities to check for. This cannot be null. /// </param> /// <param name="permissions"> /// The permissions to check for. This cannot be null. /// </param> /// <exception cref="ArgumentException"> /// <paramref name="permissions"/> cannot contain null. /// </exception> /// <returns> /// True if the current user has all the specified permissions to the specified /// entity, false otherwise. /// </returns> /// <exception cref="ArgumentNullException"> /// No argument can be null. /// </exception> /// <exception cref="InvalidOperationException"> /// <see cref="RequestContext"/> must be set. /// </exception> public IDictionary <long, bool> Check(IList <EntityRef> entities, IList <EntityRef> permissions) { if (entities == null) { throw new ArgumentNullException("entities"); } if (entities.Contains(null)) { throw new ArgumentException("Cannot check access for null entities", "entities"); } if (permissions == null) { throw new ArgumentNullException("permissions"); } if (permissions.Contains(null)) { throw new ArgumentException(@"Cannot contain null", "permissions"); } if (!RequestContext.IsSet) { throw new InvalidOperationException("RequestContext not set"); } if (entities.Count == 0) { return(new Dictionary <long, bool>()); } if (SkipCheck( )) { return(entities.ToDictionarySafe(x => x.Id, x => true)); } // Only process the most specific permission IList <EntityRef> permissionsOptimised = permissions; if (permissions.Count > 1) { long mostSpecificPermission = Permissions.MostSpecificPermission(permissions.Select(perm => perm.Id)); permissionsOptimised = new List <EntityRef> { new EntityRef(mostSpecificPermission) }; } IDictionary <long, bool> result; using (MessageContext messageContext = new MessageContext(MessageName, GetBehavior(entities.Select(e => e.Id)))) { if (!AccessControl.EntityAccessControlChecker.SkipCheck(new EntityRef(RequestContext.GetContext().Identity.Id))) { WriteHeaderMessage(entities, permissionsOptimised, messageContext); result = EntityAccessControlChecker.CheckAccess(entities, permissionsOptimised, RequestContext.GetContext().Identity.Id); WriteFooterMessage(result, messageContext); if (ShouldWriteSecurityTraceMessage(result)) { WriteSecurityTraceMessage(messageContext); } } else { result = entities.ToDictionary(e => e.Id, e => true); } } 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); }