/// <summary> /// Enqueue changes. /// </summary> /// <param name="entries">Changes to process.</param> public void AddToQueue(List <SearchResultEntry> entries) { if ((entries != null) && entries.Any()) { lock (mQueueLock) { // Add items to query Queue.AddRange(entries); // Sort query by uSNChanged and process users first Queue.Sort((a, b) => { if (LdapHelper.IsUser(a) ^ LdapHelper.IsUser(b)) { return(LdapHelper.IsUser(a) ? -1 : 1); } return(LdapHelper.GetUsnChanged(a).CompareTo(LdapHelper.GetUsnChanged(b))); }); // Start sending changes if (DispatchAllowed && !DispatchRunning) { DispatchRunning = true; Task.Factory.StartNew(SendChanges, TaskCreationOptions.LongRunning); } } } }
/// <summary> /// Handle single user. /// </summary> /// <param name="entry">User LDAP object.</param> private void HandleUser(SearchResultEntry entry) { // Create CMS object from LDAP object var user = new User( LdapHelper.GetObjectGuid(entry), LdapHelper.GetAttributeString(entry.Attributes["name"], true), LdapHelper.IsUserEnabled(entry), UserBindings.Select(k => new KeyValuePair <string, string>(k.Cms, LdapHelper.GetAttributeString(entry.Attributes[k.Ldap]))).ToList()); // Find existing object in LDAP replica var existing = Replica.Users.FirstOrDefault(u => u.Guid == user.Guid); if (LdapHelper.IsDeleted(entry)) { if (existing != null) { // Remove user Sender.RemoveUser(existing); Replica.Users.Remove(existing); } } else if (existing != null) { // Check if any attribute has changed var userXml = Sender.GetUser(user.Guid); if (!string.IsNullOrEmpty(userXml)) { bool userChanged = User.InternalBindings.Any( b => RestHelper.GetAttributeFromReponse(userXml, b.Value) != ((b.Key == "userAccountControl" ? LdapHelper.IsUserEnabled(entry).ToString().ToLowerInvariant() : LdapHelper.GetAttributeString(entry.Attributes[b.Key], b.Key == "name")) ?? string.Empty)); userChanged |= UserBindings.Any( b => RestHelper.GetAttributeFromReponse(userXml, b.Cms) != (LdapHelper.GetAttributeString(entry.Attributes[b.Ldap]) ?? string.Empty)); if (userChanged) { // Modify user Sender.ModifyUser(user); } } } else { // Add user long?userId = Sender.AddUser(user); if (userId != null) { user.Id = userId.Value; user.DistinguishedName = entry.DistinguishedName; Replica.Users.Add(user); } } }
/// <summary> /// Send changes to CMS one-by-one. /// </summary> private void SendChanges() { // Re-enumerate queue in each iteration while (Queue.Any() && DispatchAllowed) { lock (mQueueLock) { try { if (Replica == null) { LoadDirecotryReplica(); } // Process first entry var entry = Queue.First(); if (entry == null) { // Remove all nulls Queue.RemoveAll(e => e == null); continue; } // Handle incoming change if (LdapHelper.IsUser(entry)) { HandleUser(entry); } else if (LdapHelper.IsGroup(entry)) { HandleGroup(entry); } Queue.Remove(entry); // Set actual uSNChanged attribute long newUsn = LdapHelper.GetUsnChanged(entry); if ((Replica != null) && (Replica.HighestUsnChanged < newUsn)) { Replica.HighestUsnChanged = newUsn; } SaveDirecotryReplica(); } catch (Exception ex) { LogError("Exception occurred when processing object.", ex); } } } DispatchRunning = false; }
/// <summary> /// Handle single role. /// </summary> /// <param name="entry">Group LDAP object.</param> private void HandleGroup(SearchResultEntry entry) { // Create CMS object from LDAP object var role = new Role(LdapHelper.GetObjectGuid(entry), LdapHelper.GetAttributeString(entry.Attributes["sAMAccountName"], true), LdapHelper.GetAttributeString(entry.Attributes["displayName"]), GroupBindings.Select(k => new KeyValuePair <string, string>(k.Cms, LdapHelper.GetAttributeString(entry.Attributes[k.Ldap]))).ToList()); var existing = Replica.Groups.FirstOrDefault(g => g.Guid == role.Guid); List <User> currentMembers = (existing == null) ? new List <User>() : Replica.Bindings.Where(b => b.RoleId == existing.Id).SelectMany(b => Replica.Users.Where(u => u.Id == b.UserId)).ToList(); List <User> newMembers = LdapHelper.GetGroupMembers(entry).SelectMany(d => Replica.Users.Where(u => string.Equals(u.DistinguishedName, d, StringComparison.InvariantCultureIgnoreCase))).ToList(); if (LdapHelper.IsDeleted(entry)) { if (existing != null) { // Delete role Sender.RemoveRole(existing); Replica.Groups.Remove(existing); } } else { if (existing != null) { role.Id = existing.Id; // Check if any attribute has changed var roleXml = Sender.GetRole(role.Id); if (!string.IsNullOrEmpty(roleXml)) { bool roleChanged = Role.InternalBindings.Any( b => RestHelper.GetAttributeFromReponse(roleXml, b.Value) != (LdapHelper.GetAttributeString(entry.Attributes[b.Key], b.Key == "sAMAccountName") ?? string.Empty)); roleChanged |= GroupBindings.Any( b => RestHelper.GetAttributeFromReponse(roleXml, b.Cms) != (LdapHelper.GetAttributeString(entry.Attributes[b.Ldap]) ?? string.Empty)); if (roleChanged) { // Modify role Sender.ModifyRole(role); } } } else { // Add role long?roleId = Sender.AddRole(role); if (roleId != null) { role.Id = roleId.Value; Replica.Groups.Add(role); } } // Add members var addedMembers = newMembers.Where(m => currentMembers.All(c => c.Guid != m.Guid)).ToList(); foreach (var member in addedMembers) { var userroleId = Sender.AddUserToRole(member.Id, role.Id); if (userroleId != null) { Replica.Bindings.Add(new UserRoleBinding(member.Id, role.Id) { Id = userroleId.Value }); } } // Remove members var removedMembers = currentMembers.Where(m => newMembers.All(c => c.Guid != m.Guid)) .SelectMany(m => Replica.Bindings.Where(b => (b.RoleId == role.Id) && (b.UserId == m.Id))) .ToList(); foreach (var member in removedMembers) { Sender.RemoveUserFromRole(member.Id); Replica.Bindings.Remove(member); } } }
/// <summary> /// Asynchronous callback that processes changes from Active Directory. /// </summary> /// <param name="asyncResult">Result of permanens search</param> private void RunAsyncSearch(IAsyncResult asyncResult) { var results = new List <SearchResultEntry>(); // Get changes if (!asyncResult.IsCompleted) { PartialResultsCollection partialResults = null; try { partialResults = Connection.GetPartialResults(asyncResult); } catch (Exception e) { LogError("Retrieving partial results from Active Directory asynchronous search failed.", e); } if (partialResults != null) { // Add only users and groups results.AddRange(partialResults.OfType <SearchResultEntry>().Where(p => LdapHelper.IsUser(p, PersonObjectCategory) || LdapHelper.IsGroup(p, GroupObjectCategory))); } } else { LogMessage("The change notification control unexpectedly ended the search."); mSearches.Remove(asyncResult); StartIncrementalSynchronization(); } // Send changes to CMS Dispatcher.AddToQueue(results); }
/// <summary> /// Retrieves current state of LDAP database and reflects it to the CMS. /// </summary> public void Synchronize() { try { var request = new SearchRequest(DefaultNamingContext, "(&(|(objectClass=user)(objectClass=group))(usnchanged>=" + (Dispatcher.HighestUsnChanged + 1) + "))", SearchScope.Subtree, null); request.Controls.Add(new ShowDeletedControl()); // Page result var prc = new PageResultRequestControl(5); request.Controls.Add(prc); var soc = new SearchOptionsControl(System.DirectoryServices.Protocols.SearchOption.DomainScope); request.Controls.Add(soc); while (true) { var searchResponse = (SearchResponse)Connection.SendRequest(request); if (searchResponse != null) { // Find the returned page response control foreach (DirectoryControl control in searchResponse.Controls) { if (control is PageResultResponseControl) { //update the cookie for next set prc.Cookie = ((PageResultResponseControl)control).Cookie; break; } } Dispatcher.AddToQueue(searchResponse.Entries.Cast <SearchResultEntry>().Where(p => LdapHelper.IsUser(p, PersonObjectCategory) || LdapHelper.IsGroup(p, GroupObjectCategory)).ToList()); if (prc.Cookie.Length == 0) { break; } } else { break; } } } catch (Exception ex) { LogError("Full synchronization failed.", ex); } }