using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Security.Principal; using System.Text; using System.Threading.Tasks; namespace Disco.Services.Interop.ActiveDirectory { public class ActiveDirectoryGroupCache { private ConcurrentDictionary> securityIdentifierCache; private ConcurrentDictionary> distinguishedNameCache; private const long CacheTimeoutTicks = 6000000000; // 10 Minutes private const int CacheCleanIntervalMinutes = 15; private DateTime cacheCleanNext; private object cacheCleanLock = new object(); private Task cacheCleanTask; public ActiveDirectoryGroupCache() { this.securityIdentifierCache = new ConcurrentDictionary>(); this.distinguishedNameCache = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); cacheCleanNext = DateTime.Now.AddMinutes(CacheCleanIntervalMinutes); } public ADGroup GetGroup(string DistinguishedName) { // Check Cache Tuple groupRecord = TryDistinguishedNameCache(DistinguishedName); if (groupRecord == null) { // Load from AD var group = ActiveDirectory.RetrieveADGroupByDistinguishedName(DistinguishedName); SetValue(group); return group; } else { // Return from Cache return groupRecord.Item1; } } public ADGroup GetGroup(SecurityIdentifier SecurityIdentifier) { // Check Cache Tuple groupRecord = TrySecurityIdentifierCache(SecurityIdentifier); if (groupRecord == null) { // Load from AD var group = ActiveDirectory.RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier); SetValue(group); return group; } else { // Return from Cache return groupRecord.Item1; } } public IEnumerable GetRecursiveGroups(IEnumerable DistinguishedNames) { List groups = new List(); foreach (var distinguishedName in DistinguishedNames) foreach (var group in GetGroupsRecursive(distinguishedName, new Stack())) if (!groups.Contains(group)) { groups.Add(group); yield return group; } } public IEnumerable GetRecursiveGroups(string DistinguishedName) { foreach (var group in GetGroupsRecursive(DistinguishedName, new Stack())) yield return group; } private IEnumerable GetGroupsRecursive(string DistinguishedName, Stack RecursiveTree) { var group = GetGroup(DistinguishedName); if (group != null && !RecursiveTree.Contains(group)) { yield return group; if (group.MemberOf != null) { RecursiveTree.Push(group); foreach (var parentDistinguishedName in group.MemberOf) foreach (var parentGroup in GetGroupsRecursive(parentDistinguishedName, RecursiveTree)) yield return parentGroup; RecursiveTree.Pop(); } } } private Tuple TryDistinguishedNameCache(string DistinguishedName) { Tuple groupRecord; if (distinguishedNameCache.TryGetValue(DistinguishedName, out groupRecord)) { if (groupRecord.Item2 > DateTime.Now) return groupRecord; else { if (distinguishedNameCache.TryRemove(DistinguishedName, out groupRecord)) securityIdentifierCache.TryRemove(groupRecord.Item1.SecurityIdentifier, out groupRecord); } } return null; } private Tuple TrySecurityIdentifierCache(SecurityIdentifier SecurityIdentifier) { Tuple groupRecord; if (securityIdentifierCache.TryGetValue(SecurityIdentifier, out groupRecord)) { if (groupRecord.Item2 > DateTime.Now) return groupRecord; else { if (securityIdentifierCache.TryRemove(SecurityIdentifier, out groupRecord)) distinguishedNameCache.TryRemove(groupRecord.Item1.DistinguishedName, out groupRecord); } } return null; } private bool SetValue(ADGroup Group) { Tuple groupRecord = Tuple.Create(Group, DateTime.Now.AddTicks(CacheTimeoutTicks)); Tuple oldGroupRecord; var distinguishedName = Group.DistinguishedName; var securityIdentifier = Group.SecurityIdentifier; if (distinguishedNameCache.ContainsKey(distinguishedName)) { if (distinguishedNameCache.TryGetValue(distinguishedName, out oldGroupRecord)) { distinguishedNameCache.TryUpdate(distinguishedName, groupRecord, oldGroupRecord); } } else { distinguishedNameCache.TryAdd(distinguishedName, groupRecord); } if (securityIdentifierCache.ContainsKey(securityIdentifier)) { if (securityIdentifierCache.TryGetValue(securityIdentifier, out oldGroupRecord)) { securityIdentifierCache.TryUpdate(securityIdentifier, groupRecord, oldGroupRecord); } } else { securityIdentifierCache.TryAdd(securityIdentifier, groupRecord); } return true; } #region Stale Cache Clean private void EnsureCleanCache() { if (cacheCleanTask == null && cacheCleanNext < DateTime.Now) { lock (cacheCleanLock) { if (cacheCleanTask == null && cacheCleanNext < DateTime.Now) { cacheCleanTask = Task.Factory.StartNew(CleanCache); } } } } private void CleanCache() { DateTime now = DateTime.Now; // Clean Cache var dnKeys = distinguishedNameCache.Keys.ToArray(); foreach (var dnKey in dnKeys) { Tuple groupRecord; if (distinguishedNameCache.TryGetValue(dnKey, out groupRecord)) { if (groupRecord.Item2 <= now) { distinguishedNameCache.TryRemove(dnKey, out groupRecord); } } } // Clean SID Cache var siKeys = securityIdentifierCache.Keys.ToArray(); foreach (var siKey in siKeys) { Tuple groupRecord; if (securityIdentifierCache.TryGetValue(siKey, out groupRecord)) { if (groupRecord.Item2 <= now) { securityIdentifierCache.TryRemove(siKey, out groupRecord); } } } // Schedule Next Clean cacheCleanNext = now.AddMinutes(CacheCleanIntervalMinutes); cacheCleanTask = null; } #endregion } }