Exemple #1
0
        /// <summary>
        /// Creates a consistent base <see cref="ComposedRuleResult"/> instance with default values
        /// </summary>
        /// <param name="entry">The entry for which we are creating a result</param>
        /// <param name="isValuePresent">Allows calling code to know if the <paramref name="entry"/> has a value for the given <see cref="AttributeName"/></param>
        /// <returns></returns>
        protected ComposedRuleResult InitResult(SearchResultEntry entry, out bool isValuePresent)
        {
            isValuePresent = false;

            var result = new ComposedRuleResult()
            {
                AttributeName           = this.AttributeName,
                EntityDistinguishedName = entry.Attributes[StringLiterals.DistinguishedName][0].ToString(),
                ObjectType     = ComposedRule.GetObjectType(entry),
                OriginalValue  = null,
                ProposedAction = ActionType.None
            };

            if (entry.Attributes.Contains(this.AttributeName))
            {
                isValuePresent       = true;
                result.OriginalValue = entry.Attributes[this.AttributeName][0].ToString();
            }

            return(result);
        }
Exemple #2
0
 /// <summary>
 /// When implemented by child classes executes this rule and returns a result
 /// </summary>
 /// <param name="parent">The composed rule containing this rule</param>
 /// <param name="entry">The search result we are checking</param>
 /// <param name="attributeValue">The current attribute value as pass through the chain</param>
 /// <returns>Either a success or error result</returns>
 public abstract RuleResult Execute(ComposedRule parent, SearchResultEntry entry, string attributeValue);
Exemple #3
0
        /// <summary>
        /// Executes a rule collection against the supplied connection
        /// </summary>
        /// <param name="collection">The rule collection to execute</param>
        /// <param name="connection">The connection used to retrieve entries for processing</param>
        /// <returns><see cref="RuleCollectionResult"/> or null if canceled</returns>
        private RuleCollectionResult ExecuteRuleCollection(RuleCollection collection, LdapConnection connection)
        {
            // these count all the totals for the connection against which this RuleCollection is being run
            var  stopWatch      = new Stopwatch();
            long skipCount      = 0;
            long entryCount     = 0;
            long duplicateCount = 0;
            long errorCount     = 0;

            this.OnStatusUpdate?.Invoke(StringLiterals.LdapConnectionEstablishing);
            var searchRequest = collection.CreateSearchRequest();

            this.OnStatusUpdate?.Invoke(StringLiterals.LdapConnectionEstablished);

            var errors = new List <ComposedRuleResult>();

            this.OnStatusUpdate?.Invoke(StringLiterals.BeginningQuery);

            while (true)
            {
                var searchResponse = (SearchResponse)connection.SendRequest(searchRequest);

                // verify support for paged results
                if (searchResponse.Controls.Length != 1 || !(searchResponse.Controls[0] is PageResultResponseControl))
                {
                    this.OnStatusUpdate?.Invoke(StringLiterals.CannotPageResultSet);
                    throw new InvalidOperationException(StringLiterals.CannotPageResultSet);
                }

                foreach (SearchResultEntry entry in searchResponse.Entries)
                {
                    if (this.CancellationPending)
                    {
                        return(null);
                    }

                    if (collection.Skip(entry))
                    {
                        skipCount++;
                        continue;
                    }

                    // this tracks the number of entries we have processed and not skipped
                    entryCount++;

                    foreach (var composedRule in collection.Rules)
                    {
                        // run each composed rule which can produce multiple results
                        var results = composedRule.Execute(entry);

                        for (var i = 0; i < results.Length; i++)
                        {
                            var result = results[i];
                            if (!result.Success)
                            {
                                errorCount++;

                                if (result.Results.Any(r => (r.ErrorTypeFlags & ErrorType.Duplicate) != 0))
                                {
                                    duplicateCount++;

                                    if (result.ProposedAction == ActionType.Edit)
                                    {
                                        // Add original LDAP entry with the same value.
                                        var originalEntry    = DuplicateStore.GetOriginalSearchResultEntry(result.AttributeName, result.OriginalValue);
                                        var additionalResult = new ComposedRuleResult
                                        {
                                            AttributeName           = result.AttributeName,
                                            EntityDistinguishedName = originalEntry.DistinguishedName,
                                            EntityCommonName        = originalEntry.Attributes[StringLiterals.Cn][0].ToString(),
                                            ObjectType     = ComposedRule.GetObjectType(entry),
                                            OriginalValue  = result.OriginalValue,
                                            ProposedAction = result.ProposedAction,
                                            ProposedValue  = result.ProposedValue,
                                            Results        = new RuleResult[] {
                                                new RuleResult(false)
                                                {
                                                    ErrorTypeFlags = ErrorType.Duplicate,
                                                    ProposedAction = result.ProposedAction,
                                                    UpdatedValue   = result.OriginalValue
                                                }
                                            },
                                            Success = result.Success
                                        };
                                        errors.Add(additionalResult);
                                    }
                                }

                                errors.Add(result);
                            }
                        }
                    }
                }

                // handle paging
                var cookie = searchResponse.Controls.OfType <PageResultResponseControl>().First().Cookie;

                // if this is true, there are no more pages to request
                if (cookie.Length == 0)
                {
                    break;
                }

                searchRequest.Controls.OfType <PageResultRequestControl>().First().Cookie = cookie;
            }

            // we are all done, stop tracking time
            stopWatch.Stop();

            return(new RuleCollectionResult
            {
                TotalDuplicates = duplicateCount,
                TotalErrors = errorCount,
                TotalFound = skipCount + entryCount,
                TotalSkips = skipCount,
                TotalProcessed = entryCount,
                Elapsed = stopWatch.Elapsed,
                Errors = errors.ToArray()
            });
        }
Exemple #4
0
 /// <summary>
 /// Gets the object type as a string for the given <paramref name="entry"/>
 /// </summary>
 /// <param name="entry"><see cref="SearchResultEntry"/> whose object type we want</param>
 /// <returns></returns>
 protected string GetObjectType(SearchResultEntry entry)
 {
     return(ComposedRule.GetObjectType(entry));
 }
        /// <summary>
        /// Logic to determine if a given <see cref="SearchResultEntry"/> is skipped for processing
        /// </summary>
        /// <param name="entry"><see cref="SearchResultEntry"/> to check</param>
        /// <returns>True if <paramref name="entry"/> should be skipped, false if it should be processed</returns>
        public override bool Skip(SearchResultEntry entry)
        {
            string objectDn = String.Empty;

            try
            {
                objectDn = entry.Attributes[StringLiterals.DistinguishedName][0].ToString();

                // Active Directory Synchronization in Office 365
                // http://technet.microsoft.com/en-us/library/hh852469.aspx#bkmk_adcleanup
                // Remove duplicate proxyAddress and userPrincipalName attributes.
                // Update blank and invalid userPrincipalName attributes with a valid userPrincipalName.
                // Remove invalid and questionable characters in the givenName, surname (sn), sAMAccountName, displayName,
                // mail, proxyAddresses, mailNickname, and userPrincipalName attributes.

                // Appendix F Directory Object Preparation
                // http://technet.microsoft.com/en-us/library/hh852533.aspx

                // Any object is filtered if:
                // match well known exclusion pattern
                if (entry.Attributes[StringLiterals.Cn][0].ToString().EndsWith("$", StringComparison.CurrentCultureIgnoreCase))
                {
                    return(true);
                }

                foreach (string exclusion in Constants.WellKnownExclusions)
                {
                    if (entry.Attributes[StringLiterals.Cn][0].ToString().ToUpperInvariant().StartsWith(exclusion.ToUpperInvariant(), StringComparison.CurrentCultureIgnoreCase))
                    {
                        return(true);
                    }
                }

                // •Object is a conflict object (DN contains \0ACNF: )
                if (entry.Attributes[StringLiterals.DistinguishedName][0].ToString().IndexOf("\0ACNF:", StringComparison.CurrentCultureIgnoreCase) != -1)
                {
                    return(true);
                }

                //  •isCriticalSystemObject is present
                if (entry.Attributes.Contains(StringLiterals.IsCriticalSystemObject))
                {
                    if (Convert.ToBoolean(entry.Attributes[StringLiterals.IsCriticalSystemObject][0].ToString(), CultureInfo.CurrentCulture) == true)
                    {
                        return(true);
                    }
                }

                // determine objectClass
                string objectType = ComposedRule.GetObjectType(entry);

                // User objects are filtered if:
                if (objectType.Equals("user", StringComparison.CurrentCultureIgnoreCase))
                {
                    if (entry.Attributes.Contains(StringLiterals.SamAccountName))
                    {
                        // SamAccountName exclusions
                        if (entry.Attributes[StringLiterals.SamAccountName][0].ToString().ToUpperInvariant().StartsWith("CAS_", StringComparison.CurrentCultureIgnoreCase) ||
                            entry.Attributes[StringLiterals.SamAccountName][0].ToString().ToUpperInvariant().StartsWith("SUPPORT_", StringComparison.CurrentCultureIgnoreCase) ||
                            entry.Attributes[StringLiterals.SamAccountName][0].ToString().ToUpperInvariant().StartsWith("MSOL_", StringComparison.CurrentCultureIgnoreCase))
                        {
                            return(true);
                        }
                    }
                    else
                    {
                        //  •sAMAccountName is not present
                        //return true;
                    }

                    if (entry.Attributes.Contains(StringLiterals.MailNickName))
                    {
                        // mailNickName exclusions
                        if (entry.Attributes[StringLiterals.MailNickName][0].ToString().ToUpperInvariant().StartsWith("CAS_", StringComparison.CurrentCultureIgnoreCase) ||
                            entry.Attributes[StringLiterals.MailNickName][0].ToString().ToUpperInvariant().StartsWith("SYSTEMMAILBOX", StringComparison.CurrentCultureIgnoreCase))
                        {
                            return(true);
                        }
                    }

                    if (entry.Attributes.Contains(StringLiterals.DisplayName) &&
                        entry.Attributes.Contains(StringLiterals.MsExchHideFromAddressLists))
                    {
                        if (Convert.ToBoolean(entry.Attributes[StringLiterals.MsExchHideFromAddressLists][0].ToString(), CultureInfo.CurrentCulture) == true &&
                            entry.Attributes[StringLiterals.DisplayName][0].ToString().ToUpperInvariant().StartsWith("MSOL", StringComparison.CurrentCultureIgnoreCase))
                        {
                            return(true);
                        }
                    }

                    //  •msExchRecipientTypeDetails == (0x1000 OR 0x2000 OR 0x4000 OR 0x400000 OR 0x800000 OR 0x1000000 OR 0x20000000)
                    if (entry.Attributes.Contains(StringLiterals.MsExchRecipientTypeDetails))
                    {
                        switch (entry.Attributes[StringLiterals.MsExchRecipientTypeDetails][0].ToString())
                        {
                        case "0x1000":
                        case "0x2000":
                        case "0x4000":
                        case "0x400000":
                        case "0x800000":
                        case "0x1000000":
                        case "0x20000000":
                            return(true);
                        }
                    }
                }

                // Group objects are filter if:
                // List of attributes that are synchronized to Office 365 and attributes that are written back to the on-premises Active Directory Domain Services
                // http://support.microsoft.com/kb/2256198
                // NOTE:  these are listed as group filters in the article, but they appear to be an inaccurate mix of filters and errors.
                // SecurityEnabledGroup objects are filtered if:
                //  •isCriticalSystemObject = TRUE
                //  •mail is present AND DisplayName is not present
                //  •Group has more than 15,000 immediate members
                // MailEnabledGroup objects are filtered if:
                //  •DisplayName is empty
                //  •(ProxyAddress does not have a primary SMTP address) AND (mail attribute is not present/invalid - i.e. indexof ('@') <= 0)
                //  •Group has more than 15,000 immediate members

                if (objectType.Equals("group", StringComparison.CurrentCultureIgnoreCase))
                {
                    if (Convert.ToInt64(entry.Attributes[StringLiterals.GroupType][0].ToString(), CultureInfo.CurrentCulture) < 0)
                    // security group
                    {
                        if (!entry.Attributes.Contains(StringLiterals.Mail))
                        {
                            return(true);
                        }
                    }
                    else
                    // distribution group
                    {
                        if (!entry.Attributes.Contains(StringLiterals.ProxyAddresses))
                        {
                            return(true);
                        }
                    }
                }
                return(false);
            }
            catch (Exception ex)
            {
                this.InvokeStatus(StringLiterals.Exception + "Result Filter: " + objectDn + "  " + ex.Message);
            }

            return(false);
        }