예제 #1
0
        private void AddPermissionsToReason(AccessReason reason)
        {
            IEntityCollection <Permission> permissions = reason.AccessRule.PermissionAccess;
            IReadOnlyList <long>           permIds     = permissions.Select(perm => perm.Id).ToList();

            First                 first     = new First( );
            StringBuilder         sb        = new StringBuilder( );
            Action <string, long> checkPerm = (permName, permId) =>
            {
                if (!permIds.Contains(permId))
                {
                    return;
                }

                if (!first)
                {
                    sb.Append(", ");
                }
                sb.Append(permName);
            };

            checkPerm("Create", Permissions.Create.Id);
            checkPerm("Read", Permissions.Read.Id);
            checkPerm("Modify", Permissions.Modify.Id);
            checkPerm("Delete", Permissions.Delete.Id);

            reason.PermissionIds   = permIds;
            reason.PermissionsText = sb.ToString( );
        }
예제 #2
0
        private IEnumerable <AccessReason> FilterOverlappingReasons(IEnumerable <AccessReason> reasons)
        {
            // Eliminate duplicate reasons for a given type, if one of them applies to all instances
            IEnumerable <IGrouping <long, AccessReason> > groupedByType = reasons.GroupBy(reason => reason.TypeId);

            HashSet <AccessReason> remove = new HashSet <AccessReason>( );

            foreach (IGrouping <long, AccessReason> reasonsForType in groupedByType)
            {
                List <AccessReason> list = reasonsForType.AsList( );
                if (list.Count == 1)
                {
                    continue;
                }

                for (int i = 0; i < list.Count; i++)
                {
                    AccessReason reason1 = list [i];
                    AccessReason reason2;

                    for (int j = i + 1; j < list.Count; j++)
                    {
                        reason2 = list [j];
                        bool isSuperReason = IsSuperReason(reason2, reason1);
                        if (isSuperReason)
                        {
                            remove.Add(reason1);
                            break;
                        }
                    }
                }
            }

            return(reasons.Where(reason => !remove.Contains(reason)));
        }
예제 #3
0
        private void AddReasonsByGroup(List <AccessReason> reasons)
        {
            // Filter list of rules to suppress, otherwise things get too messy.
            string [] rulesToIgnoreByAlias = new string [] { "core:accessRuleResourceForAssignedTask", "core:accessRuleResourcesByRole" };
            long[]    rulesToIgnore        = rulesToIgnoreByAlias.Select(alias => new EntityRef(alias).Id).ToArray();
            var       filteredReasons      = reasons.Where(reason => !rulesToIgnore.Contains(reason.AccessRule.Id)).ToList();

            // Preload list of types that access rules directly apply to
            IEnumerable <long>             typeIds = reasons.Select(reason => reason.TypeId).Distinct( );
            IDictionary <long, EntityType> types   = EntityRepository.Get <EntityType>(typeIds).ToDictionary(e => e.Id);

            // First run, with scope not included in grouping
            IEnumerable <IGrouping <AccessReasonGrouping, AccessReason> > groupedReasons;

            groupedReasons = filteredReasons.GroupBy(reason => new AccessReasonGrouping(reason, AccessRuleScope.SecuredRelationship));

            // Process groups of reasons
            foreach (IGrouping <AccessReasonGrouping, AccessReason> reasonGroup in groupedReasons)
            {
                AccessReason prototypicalReason = reasonGroup.First( );

                // Build list of types in this group
                IEnumerable <EntityType> groupTypes = reasonGroup
                                                      .Select(reason => reason.TypeId).Distinct( )
                                                      .Select(typeId => types [typeId]);

                // Add reasons resulting from secured relationships
                var securedRelationshipReasons = AddSecuredRelationshipReasons(groupTypes, prototypicalReason);
                reasons.AddRange(securedRelationshipReasons.Select(r => r.Item1));

                // Add new types to dictionary
                foreach (Tuple <AccessReason, EntityType> tuple in securedRelationshipReasons)
                {
                    types [tuple.Item2.Id] = tuple.Item2;
                }
            }

            // Second run, with scope included in grouping
            filteredReasons = reasons.Where(reason => reason.AccessRule == null || !rulesToIgnore.Contains(reason.AccessRule.Id)).ToList( );
            groupedReasons  = filteredReasons.GroupBy(reason => new AccessReasonGrouping(reason));

            // Process groups of reasons
            foreach (IGrouping <AccessReasonGrouping, AccessReason> reasonGroup in groupedReasons)
            {
                AccessReason prototypicalReason = reasonGroup.First( );

                // Build list of types in this group
                IEnumerable <EntityType> groupTypes = reasonGroup
                                                      .Select(reason => reason.TypeId).Distinct( )
                                                      .Select(typeId => types [typeId]);

                // Add derived types
                var derivedTypeReasons = AddDerivedTypeReasons(groupTypes, prototypicalReason);
                reasons.AddRange(derivedTypeReasons);
            }
        }
예제 #4
0
        /// <summary>
        /// Return the list of all objects that the subject has access to, and the reason for the access.
        /// </summary>
        /// <param name="subjectId">The role or user </param>
        /// <param name="settings">Settings</param>
        /// <returns>List of access reasons.</returns>
        public IReadOnlyList <AccessReason> GetTypeAccessReasons(long subjectId, [NotNull] TypeAccessReasonSettings settings)
        {
            // Get list of applicable access rules
            // null permission = matches any permission
            // null securableEntityTypes = matches any type
            IReadOnlyList <AccessRuleQuery> queries = QueryRepository.GetQueries(subjectId, null, null).ToList( );


            // Preload all applicable access rules
            IEnumerable <long>             accessRuleIds = queries.Select(query => query.AccessRuleId);
            IDictionary <long, AccessRule> accessRules   = EntityRepository.Get <AccessRule>(accessRuleIds).ToDictionary(e => e.Id);


            // Build initial list of reasons
            List <AccessReason> reasons = new List <AccessReason>( );

            foreach (AccessRuleQuery ruleQuery in queries)
            {
                AccessRule accessRule;
                if (!accessRules.TryGetValue(ruleQuery.AccessRuleId, out accessRule))
                {
                    continue;    // assert false
                }
                bool            allInstances = ruleQuery.DoesQueryGrantAllOfTypes(ruleQuery.ControlsAccessForTypeId);
                bool            perUser      = !allInstances && ruleQuery.DoesQueryReferToCurrentUser( );
                AccessRuleScope scope        = allInstances ? AccessRuleScope.AllInstances : (perUser ? AccessRuleScope.PerUser : AccessRuleScope.SomeInstances);

                AccessReason reason = new AccessReason
                {
                    AccessRuleQuery = ruleQuery,
                    AccessRule      = accessRule,
                    SubjectId       = accessRule.AllowAccessBy?.Id ?? 0,
                    TypeId          = ruleQuery.ControlsAccessForTypeId,
                    Description     = "Access rule: " + (accessRule.AccessRuleReport?.Name ?? accessRule.Name),
                    AccessRuleScope = scope
                };
                AddPermissionsToReason(reason);
                reasons.Add(reason);
            }

            // Add implicit reasons due to relationship security
            AddReasonsByGroup(reasons);

            IEnumerable <AccessReason> result = reasons;

            result = FilterReasonsToUserTypes(result, settings);

            result = FilterOverlappingReasons(result);

            return(result.ToList( ));
        }
예제 #5
0
        private IReadOnlyList <AccessReason> AddDerivedTypeReasons(IEnumerable <EntityType> groupTypes, AccessReason prototypicalReason)
        {
            ISet <long>         derivedTypeIds = new HashSet <long>( );
            List <AccessReason> result         = new List <AccessReason>( );
            IDictionary <long, AccessReason> resultsByObject = new Dictionary <long, AccessReason>( );

            foreach (EntityType directType in groupTypes)
            {
                ISet <long> derivedTypes = PerTenantEntityTypeCache.Instance.GetDescendantsAndSelf(directType.Id);

                foreach (long derivedType in derivedTypes)
                {
                    if (derivedType == directType.Id)
                    {
                        continue;
                    }

                    AccessReason reason;
                    if (resultsByObject.TryGetValue(derivedType, out reason))
                    {
                        // Inherits additional types
                        reason.Description += ", " + directType.Name;
                    }
                    else
                    {
                        reason = new AccessReason
                        {
                            AccessRule      = null,
                            SubjectId       = prototypicalReason.SubjectId,
                            TypeId          = derivedType,
                            Description     = "Inherits access from '" + directType.Name + "' object",
                            AccessRuleScope = prototypicalReason.AccessRuleScope,       // THIS LINE IS WRONG .. the scope varies throughout the group
                            PermissionIds   = prototypicalReason.PermissionIds,
                            PermissionsText = prototypicalReason.PermissionsText
                        };
                        result.Add(reason);
                        resultsByObject [derivedType] = reason;
                    }
                }
            }
            return(result);
        }
예제 #6
0
        /// <summary>
        /// Returns true if one reason wholly ecclipses another.
        /// </summary>
        /// <param name="greater">The potentially greater reason.</param>
        /// <param name="lesser">The potentially smaller reason.</param>
        /// <returns>True if the potentially greater reason is a greater, broader reason.</returns>
        private bool IsSuperReason(AccessReason greater, AccessReason lesser)
        {
            if (greater.AccessRuleScope != AccessRuleScope.AllInstances)
            {
                return(false);
            }

            bool lesserIsSubset = !lesser.PermissionIds.Except(greater.PermissionIds).Any( );

            if (!lesserIsSubset)
            {
                return(false);
            }

            // Don't reject either rule if they both apply to all instances, and both have the same permissions, but apply to different subjects.
            bool samePerms = lesser.PermissionIds.Count == greater.PermissionIds.Count;

            if (samePerms && lesser.AccessRuleScope == AccessRuleScope.AllInstances && greater.SubjectId != lesser.SubjectId)
            {
                return(false);
            }

            return(true);
        }
예제 #7
0
 public AccessReasonGrouping(AccessReason reason, AccessRuleScope scope)
     : base(reason.SubjectId, reason.PermissionsText, scope)
 {
 }
예제 #8
0
        private IReadOnlyList <Tuple <AccessReason, EntityType> > AddSecuredRelationshipReasons(IEnumerable <EntityType> groupTypes, AccessReason prototypicalReason)
        {
            if (groupTypes == null)
            {
                throw new ArgumentNullException(nameof(groupTypes));
            }
            if (prototypicalReason == null)
            {
                throw new ArgumentNullException(nameof(prototypicalReason));
            }

            var result = new List <Tuple <AccessReason, EntityType> >( );

            // Reusable buffer
            StringBuilder sb = new StringBuilder( );

            // Determine all types reachable from types in this group
            IEnumerable <WalkStep <Tuple <Relationship, Direction, EntityType, EntityType> > > reachableTypes;

            reachableTypes = ReachableTypes(groupTypes);

            // Create a reason around each
            foreach (WalkStep <Tuple <Relationship, Direction, EntityType, EntityType> > reachableTypeStep in reachableTypes)
            {
                if (reachableTypeStep.PreviousStep == null)
                {
                    continue; // we already have direct access
                }
                Tuple <Relationship, Direction, EntityType, EntityType> tuple = reachableTypeStep.Node;
                EntityType reachableType = tuple.Item3;
                if (reachableType == null)
                {
                    continue; // may result from bad data?
                }
                // Calculate path as a list, with the original source at the front
                var curStep  = reachableTypeStep;
                var stepList = new List <Tuple <Relationship, Direction, EntityType, EntityType> >( );
                while (curStep != null && curStep.Node.Item1 != null)
                {
                    stepList.Insert(0, curStep.Node);
                    curStep = curStep.PreviousStep;
                }

                // Build descriptive text
                First first = new First( );
                sb.Clear( );
                sb.Append("Secured via ");
                EntityType prevType = null;
                foreach (var step in stepList)
                {
                    Relationship rel = step.Item1;
                    Direction    dir = step.Item2;

                    if (!first)
                    {
                        sb.Append(" -> ");
                    }

                    // Determine the relevant relationship name
                    string     relName;
                    EntityType fromType;
                    EntityType toType;
                    if (dir == Direction.Reverse)
                    {
                        relName  = rel.ToName ?? rel.Name;
                        fromType = rel.FromType;
                        toType   = rel.ToType;
                    }
                    else
                    {
                        relName  = rel.FromName ?? rel.Name + " (rev)";
                        fromType = rel.ToType;
                        toType   = rel.FromType;
                    }

                    // Prefix the relationship name with the type name for the first entry, or if the relationship leads from a derived type
                    if (prevType == null)
                    {
                        sb.Append('\'');
                        sb.Append(step.Item4.Name);
                        sb.Append("' object: ");
                    }
                    //else if ( prevType == null || PerTenantEntityTypeCache.Instance.IsDerivedFrom( fromType.Id, prevType.Id ) )
                    //{
                    //    sb.Append( fromType.Name );
                    //    sb.Append( " " );
                    //}

                    // Append relationship name
                    prevType = toType;
                    sb.Append('\'');
                    sb.Append(relName);
                    sb.Append('\'');
                }
                sb.Append(stepList.Count == 1 ? " relationship" : " relationships");

                AccessReason reason = new AccessReason
                {
                    AccessRule      = null,
                    SubjectId       = prototypicalReason.SubjectId,
                    TypeId          = reachableType.Id,
                    Description     = sb.ToString( ),
                    AccessRuleScope = AccessRuleScope.SecuredRelationship,
                    PermissionIds   = prototypicalReason.PermissionIds,
                    PermissionsText = prototypicalReason.PermissionsText
                };

                result.Add(new Tuple <AccessReason, EntityType>(reason, reachableType));
            }
            return(result);
        }