using Disco.Data.Repository; using Disco.Models.Repository; using System; using System.Collections.Generic; using System.Linq; using System.Security.Principal; namespace Disco.Services.Interop.ActiveDirectory { public static class ActiveDirectory { public const int SingleSearchResult = 1; public const int DefaultSearchResultLimit = 30; public const int MaxAllServerSearch = 30; public const int DomainControllerUnavailableMinutes = 10; private static ActiveDirectoryContext context; private static ActiveDirectoryGroupCache groupCache; private static readonly object contextInitializingLock = new object(); public static void Initialize(DiscoDataContext Database) { lock (contextInitializingLock) { context = new ActiveDirectoryContext(Database); groupCache = new ActiveDirectoryGroupCache(); } } public static ActiveDirectoryContext Context { get { if (context == null) { lock (contextInitializingLock) { if (context == null) throw new InvalidOperationException("Active Directory interoperability hasn't been initialized"); } } return context; } } public static ActiveDirectoryGroupCache GroupCache { get { if (groupCache == null) { lock (contextInitializingLock) { if (groupCache == null) throw new InvalidOperationException("Active Directory interoperability hasn't been initialized"); } } return groupCache; } } #region User Accounts public static ADUserAccount RetrieveADUserAccount(string Id, params string[] AdditionalProperties) { var domain = Context.GetDomainFromId(Id); return domain.GetAvailableDomainController().RetrieveADUserAccount(Id, AdditionalProperties); } public static ADUserAccount RetrieveADUserAccount(User User, params string[] AdditionalProperties) { var domain = Context.GetDomainFromId(User.UserId); return domain.GetAvailableDomainController().RetrieveADUserAccount(User.UserId, AdditionalProperties); } public static IEnumerable SearchADUserAccounts(string Term, bool Quick, int? ResultLimit = DefaultSearchResultLimit, params string[] AdditionalProperties) { if (string.IsNullOrWhiteSpace(Term)) throw new ArgumentNullException("Term"); var term = RelevantSearchTerm(Term, out var searchDomain); if (string.IsNullOrWhiteSpace(term)) return Enumerable.Empty(); var ldapFilter = string.Format(ADUserAccount.LdapSearchFilterTemplate, ADHelpers.EscapeLdapQuery(term)); IEnumerable searchResults; if (searchDomain != null) searchResults = searchDomain.SearchScope(ldapFilter, ADUserAccount.LoadProperties, ResultLimit); else searchResults = Context.SearchScope(ldapFilter, ADUserAccount.LoadProperties, ResultLimit); return searchResults.Select(result => result.AsADUserAccount(Quick, AdditionalProperties)); } #endregion #region Machine Accounts public static ADMachineAccount RetrieveADMachineAccount(string Id, params string[] AdditionalProperties) { var domain = Context.GetDomainFromId(Id); return domain.GetAvailableDomainController().RetrieveADMachineAccount(Id, AdditionalProperties); } public static ADMachineAccount RetrieveADMachineAccount(string Id, Guid? NetbootGUID, params string[] AdditionalProperties) { var domain = Context.GetDomainFromId(Id); return domain.GetAvailableDomainController().RetrieveADMachineAccount(Id, NetbootGUID, AdditionalProperties); } public static ADMachineAccount RetrieveADMachineAccount(string Id, Guid? UUIDNetbootGUID, Guid? MacAddressNetbootGUID, params string[] AdditionalProperties) { var domain = Context.GetDomainFromId(Id); return domain.GetAvailableDomainController().RetrieveADMachineAccount(Id, UUIDNetbootGUID, MacAddressNetbootGUID, AdditionalProperties); } #endregion #region Groups public static ADGroup RetrieveADGroup(string Id, params string[] AdditionalProperties) { var domain = Context.GetDomainFromId(Id); return domain.GetAvailableDomainController().RetrieveADGroup(Id, AdditionalProperties); } public static ADGroup RetrieveADGroupByDistinguishedName(string DistinguishedName, params string[] AdditionalProperties) { var domain = Context.GetDomainFromDistinguishedName(DistinguishedName); return domain.GetAvailableDomainController().RetrieveADGroupByDistinguishedName(DistinguishedName, AdditionalProperties); } public static ADGroup RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier SecurityIdentifier, params string[] AdditionalProperties) { var domain = Context.GetDomainFromSecurityIdentifier(SecurityIdentifier); return domain.GetAvailableDomainController().RetrieveADGroupWithSecurityIdentifier(SecurityIdentifier, AdditionalProperties); } public static IEnumerable SearchADGroups(string Term, int? ResultLimit = DefaultSearchResultLimit, params string[] AdditionalProperties) { if (string.IsNullOrWhiteSpace(Term)) throw new ArgumentNullException("Term"); var term = RelevantSearchTerm(Term, out var searchDomain); if (string.IsNullOrWhiteSpace(term)) return Enumerable.Empty(); var ldapFilter = string.Format(ADGroup.LdapSearchFilterTemplate, ADHelpers.EscapeLdapQuery(term)); IEnumerable searchResults; if (searchDomain != null) searchResults = searchDomain.SearchScope(ldapFilter, ADGroup.LoadProperties, ResultLimit); else searchResults = Context.SearchScope(ldapFilter, ADGroup.LoadProperties, ResultLimit); return searchResults.Select(result => result.AsADGroup(AdditionalProperties)); } #endregion #region Organisational Units [Obsolete("Retrieve as needed using RetrieveADOrganisationUnits(parentDistinguishedName)")] public static IEnumerable>> RetrieveADOrganisationalUnitStructure() { return Context.Domains .Select(d => d.GetAvailableDomainController()) .Select(dc => Tuple.Create(dc.Domain, dc.RetrieveADOrganisationalUnitStructure())); } #endregion #region Objects public static IADObject RetrieveADObject(string Id, bool Quick) { var domain = Context.GetDomainFromId(Id); return domain.GetAvailableDomainController().RetrieveADObject(Id, Quick); } public static IADObject RetrieveADObjectByDistinguishedName(string distinguishedName, bool quick) { var domain = Context.GetDomainFromDistinguishedName(distinguishedName); return domain.GetAvailableDomainController().RetrieveADObjectByDistinguishedName(distinguishedName, quick); } #endregion #region Helpers public static string ParseDomainAccountId(string AccountId) { return ParseDomainAccountId(AccountId, null); } public static string ParseDomainAccountId(string AccountId, string AccountDomain) { return ParseDomainAccountId(AccountId, AccountDomain, out _, out _); } public static string ParseDomainAccountId(string AccountId, out string AccountUsername) { return ParseDomainAccountId(AccountId, null, out AccountUsername); } public static string ParseDomainAccountId(string AccountId, string AccountDomain, out string AccountUsername) { return ParseDomainAccountId(AccountId, AccountDomain, out AccountUsername, out _); } public static string ParseDomainAccountId(string AccountId, out ADDomain Domain) { return ParseDomainAccountId(AccountId, null, out Domain); } public static string ParseDomainAccountId(string AccountId, string AccountDomain, out ADDomain Domain) { return ParseDomainAccountId(AccountId, AccountDomain, out _, out Domain); } public static string ParseDomainAccountId(string AccountId, out string AccountUsername, out ADDomain Domain) { return ParseDomainAccountId(AccountId, null, out AccountUsername, out Domain); } public static string ParseDomainAccountId(string AccountId, string AccountDomain, out string AccountUsername, out ADDomain Domain) { if (string.IsNullOrWhiteSpace(AccountId)) throw new ArgumentNullException("AccountId"); var slashIndex = AccountId.IndexOf('\\'); if (slashIndex < 0 && !string.IsNullOrWhiteSpace(AccountDomain)) { AccountId = AccountDomain + @"\" + AccountId; slashIndex = AccountDomain.Length; } if (slashIndex < 0) { AccountUsername = AccountId; Domain = Context.PrimaryDomain; } else { AccountUsername = AccountId.Substring(slashIndex + 1); Domain = Context.GetDomainByNetBiosName(AccountId.Substring(0, slashIndex)); } return string.Concat(Domain.NetBiosName, @"\", AccountUsername); } public static bool IsValidDomainAccountId(string AccountId) { return IsValidDomainAccountId(AccountId, out _, out _); } public static bool IsValidDomainAccountId(string AccountId, out string AccountUsername) { return IsValidDomainAccountId(AccountId, out AccountUsername, out _); } public static bool IsValidDomainAccountId(string AccountId, out ADDomain Domain) { return IsValidDomainAccountId(AccountId, out _, out Domain); } public static bool IsValidDomainAccountId(string AccountId, out string AccountUsername, out ADDomain Domain) { if (string.IsNullOrEmpty(AccountId)) { AccountUsername = null; Domain = null; return false; } var slashIndex = AccountId.IndexOf('\\'); if (slashIndex < 0) { AccountUsername = AccountId; Domain = null; return false; } else { AccountUsername = AccountId.Substring(slashIndex + 1); return Context.TryGetDomainByNetBiosName(AccountId.Substring(0, slashIndex), out Domain); } } /// /// If the AccountId Domain matches the Primary Domain, returns the Account Username without the Domain specified /// /// public static string FriendlyAccountId(string AccountId) { var slashIndex = AccountId.IndexOf('\\'); if (slashIndex > 0 && AccountId.Substring(0, slashIndex).Equals(Context.PrimaryDomain.NetBiosName, StringComparison.OrdinalIgnoreCase)) return AccountId.Substring(slashIndex + 1); else return AccountId; } private static string RelevantSearchTerm(string Term, out ADDomain Domain) { Domain = null; if (string.IsNullOrWhiteSpace(Term)) return null; var term = Term.Trim(); var domainSeperatorIndex = term.IndexOf('\\'); if (domainSeperatorIndex >= 0) { // Domain Search Restriction if (term.Length > domainSeperatorIndex + 1) { var netbiosName = term.Substring(0, domainSeperatorIndex); if (Context.TryGetDomainByNetBiosName(netbiosName, out Domain)) { return term.Substring(domainSeperatorIndex + 1); } else { return null; // Unknown Domain } } else { return null; // No term to search, only Domain } } return term; } #endregion } }