/// <summary> /// What entity types does the given <see cref="Subject" /> have the given <see cref="Permission" /> to? /// </summary> /// <param name="permission"> /// The <see cref="Permission" /> to check. This cannot be null. /// </param> /// <param name="subject"> /// The user or role to check. This cannot be null. /// </param> /// <returns> /// The list of <see cref="EntityType" />s the user has access to. /// </returns> protected override IEnumerable <long> AllowedEntityTypes(EntityRef permission, Subject subject) { if (permission == null) { throw new ArgumentNullException(nameof(permission)); } if (subject == null) { throw new ArgumentNullException(nameof(subject)); } var accessQueries = QueryRepository.GetQueries(subject.Id, permission, new long[] { -1 }); IReadOnlyCollection <long> entityTypes = accessQueries .Select(x => x.ControlsAccessForTypeId) .ToList(); return(entityTypes); }
/// <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)); } } } } }