Files
Disco/Disco.Services/Interop/ActiveDirectory/ActiveDirectoryContext.cs
T
Gary Sharp a819d2722a Feature #49: Active Directory Managed Groups
Document Template Attachments, Device Batches, Device Profiles and User
Flags can be associated with an Active Directory group. This AD group is
then automatically synchronized with relevant User/Machine accounts.
Contains various other UI tweaks and configuration enhancements.
2014-06-16 22:21:31 +10:00

315 lines
12 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 Contructor/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;
// 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 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
}
}