namespace DSInternals.DataStore { using DSInternals.Common.Data; using DSInternals.Common.Exceptions; using Microsoft.Database.Isam; using System; using System.Collections.Generic; using System.Linq; /// /// Performs translation between Distinguished Names and DN Tags. /// public class DistinguishedNameResolver : IDisposable { private Cursor cursor; private DirectorySchema schema; /// /// Distinguished name cache /// private IDictionary dnCache; public DistinguishedNameResolver(IsamDatabase database, DirectorySchema schema) { // Initialize the DN cache, while pre-caching the root DN as a sentinel. this.dnCache = new Dictionary(); this.dnCache.Add(ADConstants.RootDNTag, new DistinguishedName()); // Cache AD schema and datatable cursor this.schema = schema; this.cursor = database.OpenCursor(ADConstants.DataTableName); } /// /// Recursively resolves a DN Tag to a full distinguished name. /// /// Distinguished name tag /// Resolved DN /// public DistinguishedName Resolve(int dnTag) { // Check the DN cache first DistinguishedName dnFromCache = this.TryResolveFromCache(dnTag); if (dnFromCache != null) { // We just return the DN from cache. return dnFromCache; } if (dnTag < ADConstants.RootDNTag) { // Minimum DN Tag is 2. throw new ArgumentOutOfRangeException("dnTag"); } // Cache column IDs var dntColId = schema.FindColumnId(CommonDirectoryAttributes.DNTag); var pdntColId = schema.FindColumnId(CommonDirectoryAttributes.ParentDNTag); var rdnColId = schema.FindColumnId(CommonDirectoryAttributes.RDN); var rdnTypeColId = schema.FindColumnId(CommonDirectoryAttributes.RDNType); // Set index to the Distinguished Name Tag (~primary key) cursor.CurrentIndex = schema.FindIndexName(CommonDirectoryAttributes.DNTag); // We will build the DN from leaf to root DistinguishedName result = new DistinguishedName(); int currentDNTag = dnTag; while (currentDNTag != ADConstants.RootDNTag) { // Move cursor to the current object bool found = cursor.GotoKey(Key.Compose(currentDNTag)); if (!found) { throw new DirectoryObjectNotFoundException(dnTag); } // Retrieve the current object's RDN, e.g. CN=Administrator string name = cursor.RetrieveColumnAsString(rdnColId); int rdnType = cursor.RetrieveColumnAsInt(rdnTypeColId).Value; string rdnAtt = schema.FindAttribute(rdnType).Name.ToUpper(); var currentRDN = new DistinguishedNameComponent(rdnAtt, name); // Concat the current RDN with the child RDN in result result.AddParent(currentRDN); // Identify the current object's parent int parentDNTag = cursor.RetrieveColumnAsDNTag(pdntColId).Value; // Check the DN cache DistinguishedName parentDN = this.TryResolveFromCache(parentDNTag); if(parentDN != null) { // We have found the parent object in DN cache. result.AddParent(parentDN); // Add the current object to cache if the parent is DC or root. bool shoulCache = parentDN.Components.Count == 0 || String.Equals(parentDN.Components[0].Name, CommonDirectoryAttributes.DomainComponent, StringComparison.OrdinalIgnoreCase); if(shoulCache) { var currentDN = new DistinguishedName(currentRDN); currentDN.AddParent(parentDN); this.dnCache.Add(currentDNTag, currentDN); } // We can stop the recursion as we have resolved the entire DN using cache. break; } // Move upwards to the object's parent as we have not found it in the DN cache. currentDNTag = parentDNTag; } return result; } /// /// Translates a distinguished name to a its DN Tag. /// /// Distinguished name to translate /// DN Tag /// public int Resolve(string dn) { var parsed = new DistinguishedName(dn); return this.Resolve(parsed); } /// /// Translates a distinguished name to a its DN Tag. /// /// Distinguished name to translate /// DN Tag /// public int Resolve(DistinguishedName dn) { if (dn.Components.Count == 0) { throw new ArgumentException("Empty distinguished name provided.", "dn"); } // Get the PDNT_index cursor.CurrentIndex = this.schema.FindIndexName(CommonDirectoryAttributes.ParentDNTag); // Start at the root object int currentDNTag = ADConstants.RootDNTag; foreach (var component in dn.Components.Reverse()) { // Indexed columns: PDNT_col, name bool found = cursor.GotoKey(Key.Compose(currentDNTag, component.Value)); if (!found) { throw new DirectoryObjectNotFoundException(dn); } // Test AttrTyp int foundRdnAttId = cursor.RetrieveColumnAsInt(schema.FindColumnId(CommonDirectoryAttributes.RDNType)).Value; string foundRdnAttName = schema.FindAttribute(foundRdnAttId).Name; // Compare the found isRDN attribute with the requested one. Case insensitive. if (String.Compare(component.Name, foundRdnAttName, true) != 0) { throw new DirectoryObjectNotFoundException(dn); } // Move to the found object currentDNTag = cursor.RetrieveColumnAsDNTag(schema.FindColumnId(CommonDirectoryAttributes.DNTag)).Value; } return currentDNTag; } /// /// Recursively resolves multiple DN Tags to full distinguished names. /// /// DN Tags to resolve. /// Distinguished names /// public IEnumerable Resolve(IEnumerable dnTags) { foreach (int dnTag in dnTags) { yield return this.Resolve(dnTag); } } public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing && cursor != null) { cursor.Dispose(); cursor = null; } } /// /// Resolves a DN Tag to a full distinguished name using the cache. /// /// DN Tag /// Distinguished name private DistinguishedName TryResolveFromCache(int dnTag) { DistinguishedName dnFromCache; bool found = this.dnCache.TryGetValue(dnTag, out dnFromCache); return found ? dnFromCache : null; } } }