27c21175d7
Migrate much of BI to Services. Added Wireless Profile Provider plugin feature. Added Certificate Authority Provider plugin feature. Modified Certificate Provider plugin feature. Database migration v17, for Device Profiles. Enrolment Client Updated to support CA Certificates, Wireless Profiles and Hardware Info. New Client Enrolment Protocol to support new features. Plugin Manifest Generator added to main solution. Improved AD search performance.
338 lines
14 KiB
C#
338 lines
14 KiB
C#
using Disco.Data.Repository;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.DirectoryServices;
|
|
using System.DirectoryServices.ActiveDirectory;
|
|
using System.Linq;
|
|
using System.Security.Principal;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Disco.Services.Interop.ActiveDirectory
|
|
{
|
|
public class ActiveDirectoryContext
|
|
{
|
|
public ADSite Site { get; private set; }
|
|
public ADDomain PrimaryDomain { get; private set; }
|
|
public List<ADDomain> Domains { get; private set; }
|
|
public ActiveDirectoryManagedGroups ManagedGroups { get; private set; }
|
|
|
|
public List<string> ForestServers
|
|
{
|
|
get
|
|
{
|
|
return ADDiscoverForestServers.LoadForestServersBlocking();
|
|
}
|
|
}
|
|
|
|
private bool _SearchAllForestServers { get; set; }
|
|
public bool SearchAllForestServers
|
|
{
|
|
get
|
|
{
|
|
var fs = ADDiscoverForestServers.ForestServers;
|
|
if (fs != null && fs.Count > ActiveDirectory.MaxForestServerSearch)
|
|
return false; // Never
|
|
|
|
return _SearchAllForestServers;
|
|
}
|
|
}
|
|
|
|
#region Constructor/Initializing
|
|
|
|
private ActiveDirectoryContext()
|
|
{
|
|
ManagedGroups = new ActiveDirectoryManagedGroups();
|
|
}
|
|
|
|
internal ActiveDirectoryContext(DiscoDataContext Database) : this()
|
|
{
|
|
Initialize(Database);
|
|
}
|
|
|
|
private void Initialize(DiscoDataContext Database)
|
|
{
|
|
// Search Entire Forest (default: true)
|
|
this._SearchAllForestServers = Database.DiscoConfiguration.ActiveDirectory.SearchAllForestServers ?? true;
|
|
|
|
// Set Search LDAP Filters
|
|
InitializeWildcardSearchSufixOnly(Database.DiscoConfiguration.ActiveDirectory.SearchWildcardSuffixOnly);
|
|
|
|
// Determine Site
|
|
var computerSite = ActiveDirectorySite.GetComputerSite();
|
|
this.Site = new ADSite(this, computerSite);
|
|
|
|
// Determine Domains
|
|
var computerDomain = Domain.GetComputerDomain();
|
|
this.Domains = computerDomain.Forest.Domains
|
|
.Cast<Domain>()
|
|
.Select(d => new ADDomain(this, d))
|
|
.ToList();
|
|
this.PrimaryDomain = this.Domains.Where(d => d.Name == computerDomain.Name).First();
|
|
|
|
// Determine Search Scope Containers
|
|
ReinitializeSearchContainers(Database.DiscoConfiguration.ActiveDirectory.SearchContainers);
|
|
|
|
// Determine Domain Controllers
|
|
var siteDomainControllers = computerSite.Servers
|
|
.OfType<DomainController>()
|
|
.Where(dc => dc.IsReachable())
|
|
.Select(dc => new ADDomainController(this, dc, GetDomainByName(dc.Domain.Name), IsSiteServer: true, IsWritable: false));
|
|
|
|
Site.UpdateDomainControllers(siteDomainControllers);
|
|
this.Domains.ForEach(domain => domain.UpdateDomainControllers(siteDomainControllers.Where(dc => dc.Domain == domain)));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Domain Getters
|
|
|
|
public bool TryGetDomainFromDistinguishedName(string DistinguishedName, out ADDomain Domain)
|
|
{
|
|
// Find closest match
|
|
Domain = this.Domains.Where(d => DistinguishedName.EndsWith(d.DistinguishedName, StringComparison.OrdinalIgnoreCase))
|
|
.OrderByDescending(d => d.DistinguishedName.Length).FirstOrDefault();
|
|
|
|
return (Domain != null);
|
|
}
|
|
public ADDomain GetDomainFromDistinguishedName(string DistinguishedName)
|
|
{
|
|
ADDomain domain;
|
|
if (!TryGetDomainFromDistinguishedName(DistinguishedName, out domain))
|
|
throw new ArgumentException(string.Format("The distinguished name is from an unknown domain: [{0}]", DistinguishedName), "DistinguishedName");
|
|
return domain;
|
|
}
|
|
|
|
public bool TryGetDomainByNetBiosName(string NetBiosName, out ADDomain Domain)
|
|
{
|
|
Domain = this.Domains.FirstOrDefault(d => d.NetBiosName.Equals(NetBiosName, StringComparison.OrdinalIgnoreCase));
|
|
return (Domain != null);
|
|
}
|
|
public ADDomain GetDomainByNetBiosName(string NetBiosName)
|
|
{
|
|
ADDomain domain;
|
|
if (!TryGetDomainByNetBiosName(NetBiosName, out domain))
|
|
throw new ArgumentException(string.Format("The domain for specified NetBios name is unknown [{0}]", NetBiosName), "NetBiosName");
|
|
return domain;
|
|
}
|
|
|
|
public bool TryGetDomainByName(string Name, out ADDomain Domain)
|
|
{
|
|
Domain = this.Domains.FirstOrDefault(d => d.Name.Equals(Name, StringComparison.OrdinalIgnoreCase));
|
|
return (Domain != null);
|
|
}
|
|
public ADDomain GetDomainByName(string Name)
|
|
{
|
|
ADDomain domain;
|
|
if (!TryGetDomainByName(Name, out domain))
|
|
throw new ArgumentException(string.Format("The domain for specified DNS name is unknown [{0}]", Name), "Name");
|
|
return domain;
|
|
}
|
|
|
|
public bool TryGetDomainFromSecurityIdentifier(SecurityIdentifier SecurityIdentifier, out ADDomain Domain)
|
|
{
|
|
Domain = this.Domains.FirstOrDefault(d => d.SecurityIdentifier.IsEqualDomainSid(SecurityIdentifier));
|
|
return (Domain != null);
|
|
}
|
|
public ADDomain GetDomainFromSecurityIdentifier(SecurityIdentifier SecurityIdentifier)
|
|
{
|
|
ADDomain domain;
|
|
if (!TryGetDomainFromSecurityIdentifier(SecurityIdentifier, out domain))
|
|
throw new ArgumentException(string.Format("The domain for specified Security Identifier is unknown [{0}]", SecurityIdentifier.ToString()), "SecurityIdentifier");
|
|
return domain;
|
|
}
|
|
|
|
public bool TryGetDomainFromId(string Id, out ADDomain Domain)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(Id))
|
|
throw new ArgumentNullException("Id");
|
|
|
|
var slashIndex = Id.IndexOf('\\');
|
|
|
|
if (slashIndex < 0)
|
|
throw new ArgumentException(string.Format("The Id must include the Domain [{0}]", Id), "Id");
|
|
|
|
return TryGetDomainByNetBiosName(Id.Substring(0, slashIndex), out Domain);
|
|
}
|
|
public ADDomain GetDomainFromId(string Id)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(Id))
|
|
throw new ArgumentNullException("Id");
|
|
|
|
var slashIndex = Id.IndexOf('\\');
|
|
|
|
if (slashIndex < 0)
|
|
throw new ArgumentException(string.Format("The Id must include the Domain [{0}]", Id), "Id");
|
|
|
|
return GetDomainByNetBiosName(Id.Substring(0, slashIndex));
|
|
}
|
|
|
|
#endregion
|
|
|
|
public ADDirectoryEntry RetrieveDirectoryEntry(string DistinguishedName, string[] LoadProperties = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(DistinguishedName))
|
|
throw new ArgumentNullException("DistinguishedName");
|
|
|
|
var d = GetDomainFromDistinguishedName(DistinguishedName);
|
|
var dc = d.GetAvailableDomainController();
|
|
|
|
return dc.RetrieveDirectoryEntry(DistinguishedName, LoadProperties);
|
|
}
|
|
|
|
#region Searching
|
|
|
|
public IEnumerable<ADSearchResult> SearchEntireForest(string LdapFilter, string[] LoadProperties, int? ResultLimit = null)
|
|
{
|
|
var queries = this.Domains.Select(d => Tuple.Create(d, d.DistinguishedName));
|
|
|
|
return SearchInternal(queries, LdapFilter, LoadProperties, ResultLimit);
|
|
}
|
|
|
|
public IEnumerable<ADSearchResult> SearchScope(string LdapFilter, string[] LoadProperties, int? ResultLimit = null)
|
|
{
|
|
var queries = this.Domains.SelectMany(
|
|
d => d.SearchContainers ?? new List<string>() { d.DistinguishedName },
|
|
(d, scope) => Tuple.Create(d, scope));
|
|
|
|
return SearchInternal(queries, LdapFilter, LoadProperties, ResultLimit);
|
|
}
|
|
|
|
internal IEnumerable<ADSearchResult> SearchInternal(IEnumerable<Tuple<ADDomain, string>> Queries, string LdapFilter, string[] LoadProperties, int? ResultLimit)
|
|
{
|
|
var queries = Queries.ToList();
|
|
|
|
switch (queries.Count)
|
|
{
|
|
case 0: // Nothing Queried
|
|
return Enumerable.Empty<ADSearchResult>();
|
|
|
|
case 1: // Single-search
|
|
var querySingle = queries[0];
|
|
return querySingle.Item1.SearchInternal(querySingle.Item2, LdapFilter, LoadProperties, ResultLimit);
|
|
|
|
default: // Multi-search - Parallelize
|
|
|
|
var queryTasks = queries.Select(query =>
|
|
Task<IEnumerable<ADSearchResult>>.Factory.StartNew(() =>
|
|
query.Item1.SearchInternal(query.Item2, LdapFilter, LoadProperties, ResultLimit))).ToArray();
|
|
|
|
// Block
|
|
Task.WaitAll(queryTasks);
|
|
|
|
var results = queryTasks.SelectMany(t => t.Result);
|
|
if (ResultLimit.HasValue)
|
|
results = results.Take(ResultLimit.Value);
|
|
return results;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Configuration
|
|
|
|
public void UpdateWildcardSearchSuffixOnly(DiscoDataContext Database, bool SearchWildcardSuffixOnly)
|
|
{
|
|
Database.DiscoConfiguration.ActiveDirectory.SearchWildcardSuffixOnly = SearchWildcardSuffixOnly;
|
|
InitializeWildcardSearchSufixOnly(SearchWildcardSuffixOnly);
|
|
}
|
|
|
|
private void InitializeWildcardSearchSufixOnly(bool SearchWildcardSuffixOnly)
|
|
{
|
|
if (SearchWildcardSuffixOnly)
|
|
{
|
|
ADGroup.LdapSearchFilterTemplate = "(&(objectCategory=Group)(|(sAMAccountName={0}*)(name={0}*)(cn={0}*)))";
|
|
ADUserAccount.LdapSearchFilterTemplate = "(&(objectCategory=Person)(objectClass=user)(|(sAMAccountName={0}*)(displayName={0}*)(sn={0}*)(givenName={0}*)))";
|
|
}
|
|
else
|
|
{
|
|
ADGroup.LdapSearchFilterTemplate = "(&(objectCategory=Group)(|(sAMAccountName=*{0}*)(name=*{0}*)(cn=*{0}*)))";
|
|
ADUserAccount.LdapSearchFilterTemplate = "(&(objectCategory=Person)(objectClass=user)(|(sAMAccountName=*{0}*)(displayName=*{0}*)(sn=*{0}*)(givenName=*{0}*)))";
|
|
}
|
|
}
|
|
|
|
public bool UpdateSearchAllForestServers(DiscoDataContext Database, bool SearchAllForestServers)
|
|
{
|
|
if (SearchAllForestServers == false)
|
|
{
|
|
Database.DiscoConfiguration.ActiveDirectory.SearchAllForestServers = false;
|
|
this._SearchAllForestServers = false;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
var forestServers = ADDiscoverForestServers.LoadForestServersBlocking();
|
|
if (forestServers.Count <= ActiveDirectory.MaxForestServerSearch)
|
|
{
|
|
Database.DiscoConfiguration.ActiveDirectory.SearchAllForestServers = true;
|
|
this._SearchAllForestServers = true;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Database.DiscoConfiguration.ActiveDirectory.SearchAllForestServers = false;
|
|
this._SearchAllForestServers = false;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void UpdateSearchContainers(DiscoDataContext Database, IEnumerable<string> Containers)
|
|
{
|
|
Dictionary<string, List<string>> searchContainers = null;
|
|
|
|
if (Containers != null)
|
|
{
|
|
searchContainers = Containers
|
|
.Where(c => !string.IsNullOrWhiteSpace(c))
|
|
.Distinct()
|
|
.Select(c =>
|
|
{
|
|
ADDomain d;
|
|
if (TryGetDomainFromDistinguishedName(c, out d))
|
|
return Tuple.Create(d, c);
|
|
else
|
|
return null;
|
|
}).Where(i => i != null)
|
|
.GroupBy(i => i.Item1)
|
|
.ToDictionary(g => g.Key.Name.ToLower(), g => g.Select(i => i.Item2).ToList());
|
|
}
|
|
|
|
if (searchContainers == null || searchContainers.Count == 0)
|
|
{
|
|
Database.DiscoConfiguration.ActiveDirectory.SearchContainers = null;
|
|
ReinitializeSearchContainers(null);
|
|
}
|
|
else
|
|
{
|
|
Database.DiscoConfiguration.ActiveDirectory.SearchContainers = searchContainers;
|
|
ReinitializeSearchContainers(searchContainers);
|
|
}
|
|
}
|
|
|
|
private void ReinitializeSearchContainers(Dictionary<string, List<string>> Containers)
|
|
{
|
|
if (Containers == null)
|
|
{
|
|
// No search restrictions (search entire domain)
|
|
foreach (var domain in this.Domains)
|
|
domain.UpdateSearchEntireDomain();
|
|
}
|
|
else
|
|
{
|
|
// Restrict search containers
|
|
var searchContainerDomains = Containers.Join(this.Domains, ok => ok.Key, ik => ik.Name, (o, i) => Tuple.Create(o, i), StringComparer.OrdinalIgnoreCase);
|
|
foreach (var domainContainers in searchContainerDomains)
|
|
domainContainers.Item2.UpdateSearchContainers(domainContainers.Item1.Value);
|
|
|
|
// Ignore domains without configured containers
|
|
var unconfiguredContainers = this.Domains.Except(searchContainerDomains.Select(sc => sc.Item2));
|
|
foreach (var domain in unconfiguredContainers)
|
|
domain.UpdateSearchContainers(new List<string>());
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|