diff --git a/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs b/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs index 66806c81..70cb7051 100644 --- a/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs +++ b/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs @@ -9,7 +9,7 @@ namespace Disco.Services.Interop.ActiveDirectory { public class ADMachineAccount : IADObject { - internal static readonly string[] LoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "dNSHostName", "netbootGUID", "isCriticalSystemObject" }; + internal static readonly string[] LoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "userAccountControl", "dNSHostName", "description", "netbootGUID", "isCriticalSystemObject" }; internal const string LdapSamAccountNameFilterTemplate = "(&(objectCategory=computer)(sAMAccountName={0}))"; internal const string LdapNetbootGuidSingleFilterTemplate = "(&(objectCategory=computer)(netbootGUID={0}))"; internal const string LdapNetbootGuidDoubleFilterTemplate = "(&(objectCategory=computer)(|(netbootGUID={0})(netbootGUID={1})))"; @@ -23,14 +23,20 @@ namespace Disco.Services.Interop.ActiveDirectory public string SamAccountName { get; private set; } public string Name { get; private set; } - public string DisplayName { get { return this.Name; } } + public string DisplayName { get { return Name; } } + public string Description { get; private set; } public string DnsName { get; private set; } public Guid NetbootGUID { get; private set; } + public ADUserAccountControlFlags UserAccountControl { get; private set; } + public bool IsCriticalSystemObject { get; private set; } + public Dictionary LoadedProperties { get; private set; } + public bool IsDisabled { get { return UserAccountControl.HasFlag(ADUserAccountControlFlags.ADS_UF_ACCOUNTDISABLE); } } + public string ParentDistinguishedName { get @@ -39,15 +45,17 @@ namespace Disco.Services.Interop.ActiveDirectory } } - private ADMachineAccount(ADDomain Domain, string DistinguishedName, SecurityIdentifier SecurityIdentifier, string SamAccountName, string Name, string DnsName, Guid NetbootGUID, bool IsCriticalSystemObject, Dictionary LoadedProperties) + private ADMachineAccount(ADDomain Domain, string DistinguishedName, SecurityIdentifier SecurityIdentifier, string SamAccountName, string Name, string Description, string DnsName, Guid NetbootGUID, ADUserAccountControlFlags UserAccountControl, bool IsCriticalSystemObject, Dictionary LoadedProperties) { this.Domain = Domain; this.DistinguishedName = DistinguishedName; this.SecurityIdentifier = SecurityIdentifier; this.SamAccountName = SamAccountName; this.Name = Name; + this.Description = Description; this.DnsName = DnsName; this.NetbootGUID = NetbootGUID; + this.UserAccountControl = UserAccountControl; this.IsCriticalSystemObject = IsCriticalSystemObject; this.LoadedProperties = LoadedProperties; } @@ -57,19 +65,21 @@ namespace Disco.Services.Interop.ActiveDirectory if (SearchResult == null) throw new ArgumentNullException("SearchResult"); - string name = SearchResult.Value("name"); - string sAMAccountName = SearchResult.Value("sAMAccountName"); - string distinguishedName = SearchResult.Value("distinguishedName"); + var name = SearchResult.Value("name"); + var description = SearchResult.Value("description"); + var sAMAccountName = SearchResult.Value("sAMAccountName"); + var distinguishedName = SearchResult.Value("distinguishedName"); var objectSid = new SecurityIdentifier(SearchResult.Value("objectSid"), 0); var dNSName = SearchResult.Value("dNSHostName"); if (dNSName == null) dNSName = string.Format("{0}.{1}", sAMAccountName.TrimEnd('$'), SearchResult.Domain.Name); - bool isCriticalSystemObject = SearchResult.Value("isCriticalSystemObject"); + var userAccountControl = (ADUserAccountControlFlags)SearchResult.Value("userAccountControl"); + var isCriticalSystemObject = SearchResult.Value("isCriticalSystemObject"); var netbootGUID = default(Guid); - byte[] netbootGuidBytes = SearchResult.Value("netbootGUID"); + var netbootGuidBytes = SearchResult.Value("netbootGUID"); if (netbootGuidBytes != null) netbootGUID = new Guid(netbootGuidBytes); @@ -90,8 +100,10 @@ namespace Disco.Services.Interop.ActiveDirectory objectSid, sAMAccountName, name, + description, dNSName, netbootGUID, + userAccountControl, isCriticalSystemObject, additionalProperties); } @@ -100,8 +112,8 @@ namespace Disco.Services.Interop.ActiveDirectory { return new User { - UserId = this.Id, - DisplayName = this.Name + UserId = Id, + DisplayName = Name }; } @@ -119,18 +131,22 @@ namespace Disco.Services.Interop.ActiveDirectory switch (PropertyName.ToLower()) { case "name": - return new string[] { this.Name }.OfType(); + return new string[] { Name }.OfType(); + case "description": + return new string[] { Description }.OfType(); case "samaccountname": - return new string[] { this.SamAccountName }.OfType(); + return new string[] { SamAccountName }.OfType(); case "distinguishedname": - return new string[] { this.DistinguishedName }.OfType(); + return new string[] { DistinguishedName }.OfType(); case "objectsid": - return new SecurityIdentifier[] { this.SecurityIdentifier }.OfType(); + return new SecurityIdentifier[] { SecurityIdentifier }.OfType(); case "netbootguid": - return new Guid[] { this.NetbootGUID }.OfType(); + return new Guid[] { NetbootGUID }.OfType(); + case "userAccountControl": + return new int[] { (int)UserAccountControl }.OfType(); default: object[] adProperty; - if (this.LoadedProperties.TryGetValue(PropertyName, out adProperty)) + if (LoadedProperties.TryGetValue(PropertyName, out adProperty)) return adProperty.OfType(); else return Enumerable.Empty(); @@ -141,61 +157,75 @@ namespace Disco.Services.Interop.ActiveDirectory public void DeleteAccount(ADDomainController WritableDomainController) { - if (this.IsCriticalSystemObject) - throw new InvalidOperationException(string.Format("This account [{0}] is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName)); + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account [{0}] is a Critical System Active Directory Object and Disco ICT refuses to modify it", DistinguishedName)); if (!WritableDomainController.IsWritable) - throw new InvalidOperationException(string.Format("The domain controller [{0}] is not writable. This action (Offline Domain Join Provision) requires a writable domain controller.", this.Name)); + throw new InvalidOperationException(string.Format("The domain controller [{0}] is not writable. This action (Delete Account) requires a writable domain controller.", Name)); - using (ADDirectoryEntry entry = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName)) + using (ADDirectoryEntry entry = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) { entry.Entry.DeleteTree(); } } public void DeleteAccount() { - this.DeleteAccount(Domain.GetAvailableDomainController(RequireWritable: true)); + DeleteAccount(Domain.GetAvailableDomainController(RequireWritable: true)); } - private void SetNetbootGUID(ADDomainController WritableDomainController, System.Guid updatedNetbootGUID) + private void SetNetbootGUID(ADDomainController WritableDomainController, Guid updatedNetbootGUID) { - if (this.IsCriticalSystemObject) - throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName)); + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", DistinguishedName)); - using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName)) + if (NetbootGUID != updatedNetbootGUID) { - var netbootGUIDProp = deAccount.Entry.Properties["netbootGUID"]; - bool flag = netbootGUIDProp.Count > 0; - if (flag) + using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) { - netbootGUIDProp.Clear(); + var netbootGUIDProp = deAccount.Entry.Properties["netbootGUID"]; + bool flag = netbootGUIDProp.Count > 0; + if (flag) + { + netbootGUIDProp.Clear(); + } + netbootGUIDProp.Add(updatedNetbootGUID.ToByteArray()); + deAccount.Entry.CommitChanges(); + NetbootGUID = updatedNetbootGUID; } - netbootGUIDProp.Add(updatedNetbootGUID.ToByteArray()); - deAccount.Entry.CommitChanges(); } } public void SetDescription(ADDomainController WritableDomainController, string Description) { - using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName)) + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", DistinguishedName)); + + if (this.Description != Description) { - var descriptionProp = deAccount.Entry.Properties["description"]; - if (descriptionProp.Count != 1 || (descriptionProp[0] as string) != Description) + using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) { - if (descriptionProp.Count > 0) + var descriptionProp = deAccount.Entry.Properties["description"]; + if (descriptionProp.Count != 1 || (descriptionProp[0] as string) != Description) { - descriptionProp.Clear(); + if (descriptionProp.Count > 0) + { + descriptionProp.Clear(); + } + if (!string.IsNullOrEmpty(Description)) + { + descriptionProp.Add(Description); + } + deAccount.Entry.CommitChanges(); + this.Description = Description; } - if (!string.IsNullOrEmpty(Description)) - { - descriptionProp.Add(Description); - } - deAccount.Entry.CommitChanges(); } } } public void SetDescription(string Description) { - this.SetDescription(Domain.GetAvailableDomainController(RequireWritable: true), Description); + if (Description != this.Description) + { + SetDescription(Domain.GetAvailableDomainController(RequireWritable: true), Description); + } } public void SetDescription(ADDomainController WritableDomainController, Device Device) @@ -227,59 +257,76 @@ namespace Disco.Services.Interop.ActiveDirectory if (description.Length > 1024) description = description.Substring(0, 1024); - this.SetDescription(WritableDomainController, description); + if (description != Description) + { + SetDescription(WritableDomainController, description); + } } public void SetDescription(Device Device) { - this.SetDescription(Domain.GetAvailableDomainController(RequireWritable: true), Device); + SetDescription(Domain.GetAvailableDomainController(RequireWritable: true), Device); } public void DisableAccount(ADDomainController WritableDomainController) { - if (this.IsCriticalSystemObject) - throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName)); + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco ICT refuses to modify it", DistinguishedName)); - using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName)) + if (!IsDisabled) { - int accountControl = (int)deAccount.Entry.Properties["userAccountControl"][0]; - int updatedAccountControl = (accountControl | 2); - if (accountControl != updatedAccountControl) + using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) { - deAccount.Entry.Properties["userAccountControl"][0] = updatedAccountControl; - deAccount.Entry.CommitChanges(); + var accountControl = (ADUserAccountControlFlags)deAccount.Entry.Properties["userAccountControl"][0]; + if (!accountControl.HasFlag(ADUserAccountControlFlags.ADS_UF_ACCOUNTDISABLE)) + { + var updatedAccountControl = (accountControl | ADUserAccountControlFlags.ADS_UF_ACCOUNTDISABLE); + deAccount.Entry.Properties["userAccountControl"][0] = (int)updatedAccountControl; + deAccount.Entry.CommitChanges(); + UserAccountControl = updatedAccountControl; + } } } } public void DisableAccount() { - this.DisableAccount(Domain.GetAvailableDomainController(RequireWritable: true)); + if (!IsDisabled) + { + DisableAccount(Domain.GetAvailableDomainController(RequireWritable: true)); + } } public void EnableAccount(ADDomainController WritableDomainController) { - if (this.IsCriticalSystemObject) - throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName)); + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", DistinguishedName)); - using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName)) + if (IsDisabled) { - int accountControl = (int)deAccount.Entry.Properties["userAccountControl"][0]; - if ((accountControl & 2) == 2) + using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) { - int updatedAccountControl = (accountControl ^ 2); - deAccount.Entry.Properties["userAccountControl"][0] = updatedAccountControl; - deAccount.Entry.CommitChanges(); + var accountControl = (ADUserAccountControlFlags)deAccount.Entry.Properties["userAccountControl"][0]; + if (accountControl.HasFlag(ADUserAccountControlFlags.ADS_UF_ACCOUNTDISABLE)) + { + var updatedAccountControl = (accountControl ^ ADUserAccountControlFlags.ADS_UF_ACCOUNTDISABLE); + deAccount.Entry.Properties["userAccountControl"][0] = (int)updatedAccountControl; + deAccount.Entry.CommitChanges(); + UserAccountControl = updatedAccountControl; + } } } } public void EnableAccount() { - this.EnableAccount(Domain.GetAvailableDomainController(RequireWritable: true)); + if (IsDisabled) + { + EnableAccount(Domain.GetAvailableDomainController(RequireWritable: true)); + } } public bool UpdateNetbootGUID(ADDomainController WritableDomainController, string UUID, string MACAddress) { - if (this.IsCriticalSystemObject) - throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName)); + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", DistinguishedName)); Guid netbootGUID = Guid.Empty; @@ -292,9 +339,9 @@ namespace Disco.Services.Interop.ActiveDirectory netbootGUID = NetbootGUIDFromMACAddress(MACAddress); } - if (netbootGUID != System.Guid.Empty && netbootGUID != this.NetbootGUID) + if (netbootGUID != System.Guid.Empty && netbootGUID != NetbootGUID) { - this.SetNetbootGUID(WritableDomainController, netbootGUID); + SetNetbootGUID(WritableDomainController, netbootGUID); return true; } else @@ -304,20 +351,20 @@ namespace Disco.Services.Interop.ActiveDirectory } public void UpdateNetbootGUID(string UUID, string MACAddress) { - this.UpdateNetbootGUID(Domain.GetAvailableDomainController(RequireWritable: true), UUID, MACAddress); + UpdateNetbootGUID(Domain.GetAvailableDomainController(RequireWritable: true), UUID, MACAddress); } - public static System.Guid NetbootGUIDFromUUID(string UUID) + public static Guid NetbootGUIDFromUUID(string UUID) { return new Guid(UUID); } - public static System.Guid NetbootGUIDFromMACAddress(string MACAddress) + public static Guid NetbootGUIDFromMACAddress(string MACAddress) { string strippedMACAddress = MACAddress.Trim().Replace(":", string.Empty).Replace("-", string.Empty); bool flag = strippedMACAddress.Length == 12; - System.Guid NetbootGUIDFromMACAddress; + Guid NetbootGUIDFromMACAddress; if (flag) { - System.Guid guid = new System.Guid(string.Format("00000000-0000-0000-0000-{0}", strippedMACAddress)); + Guid guid = new Guid(string.Format("00000000-0000-0000-0000-{0}", strippedMACAddress)); NetbootGUIDFromMACAddress = guid; } else @@ -329,10 +376,10 @@ namespace Disco.Services.Interop.ActiveDirectory public void MoveOrganisationalUnit(ADDomainController WritableDomainController, string NewOrganisationUnit) { - if (this.IsCriticalSystemObject) - throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", this.DistinguishedName)); + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", DistinguishedName)); - var parentDistinguishedName = this.ParentDistinguishedName; + var parentDistinguishedName = ParentDistinguishedName; if (parentDistinguishedName != null && !parentDistinguishedName.Equals(NewOrganisationUnit, StringComparison.OrdinalIgnoreCase)) { @@ -341,31 +388,31 @@ namespace Disco.Services.Interop.ActiveDirectory NewOrganisationUnit = Domain.DefaultComputerContainer; if (!NewOrganisationUnit.EndsWith(Domain.DistinguishedName, StringComparison.OrdinalIgnoreCase)) - throw new InvalidOperationException(string.Format("Unable to move AD Account from one domain [{0}] to another [{1}].", this.DistinguishedName, NewOrganisationUnit)); + throw new InvalidOperationException(string.Format("Unable to move AD Account from one domain [{0}] to another [{1}].", DistinguishedName, NewOrganisationUnit)); using (ADDirectoryEntry ou = WritableDomainController.RetrieveDirectoryEntry(NewOrganisationUnit)) { - using (ADDirectoryEntry i = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName)) + using (ADDirectoryEntry i = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) { i.Entry.UsePropertyCache = false; i.Entry.MoveTo(ou.Entry); // Update Distinguished Name - this.DistinguishedName = i.Entry.Properties["distinguishedName"][0].ToString(); + DistinguishedName = i.Entry.Properties["distinguishedName"][0].ToString(); } } } } public void MoveOrganisationalUnit(string NewOrganisationUnit) { - this.MoveOrganisationalUnit(Domain.GetAvailableDomainController(RequireWritable: true), NewOrganisationUnit); + MoveOrganisationalUnit(Domain.GetAvailableDomainController(RequireWritable: true), NewOrganisationUnit); } #endregion public override string ToString() { - return this.Id; + return Id; } public override bool Equals(object obj) @@ -373,11 +420,11 @@ namespace Disco.Services.Interop.ActiveDirectory if (obj == null || !(obj is ADMachineAccount)) return false; else - return this.DistinguishedName == ((ADMachineAccount)obj).DistinguishedName; + return DistinguishedName == ((ADMachineAccount)obj).DistinguishedName; } public override int GetHashCode() { - return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this.DistinguishedName); + return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(DistinguishedName); } } } diff --git a/Disco.Services/Interop/ActiveDirectory/ADUserAccount.cs b/Disco.Services/Interop/ActiveDirectory/ADUserAccount.cs index ebda6949..bad34e75 100644 --- a/Disco.Services/Interop/ActiveDirectory/ADUserAccount.cs +++ b/Disco.Services/Interop/ActiveDirectory/ADUserAccount.cs @@ -10,8 +10,8 @@ namespace Disco.Services.Interop.ActiveDirectory { internal const string LdapSamAccountNameFilterTemplate = "(&(objectCategory=Person)(sAMAccountName={0}))"; internal static string LdapSearchFilterTemplate = "(&(objectCategory=Person)(objectClass=user)(|(sAMAccountName={0}*)(displayName={0}*)(sn={0}*)(givenName={0}*)))"; - internal static readonly string[] LoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "displayName", "sn", "givenName", "memberOf", "primaryGroupID", "mail", "telephoneNumber" }; - internal static readonly string[] QuickLoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "displayName", "sn", "givenName", "mail", "telephoneNumber" }; + internal static readonly string[] LoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "userAccountControl", "isCriticalSystemObject", "displayName", "sn", "givenName", "memberOf", "primaryGroupID", "mail", "telephoneNumber" }; + internal static readonly string[] QuickLoadProperties = { "name", "distinguishedName", "sAMAccountName", "objectSid", "userAccountControl", "isCriticalSystemObject", "displayName", "sn", "givenName", "mail", "telephoneNumber" }; public ADDomain Domain { get; private set; } @@ -30,13 +30,21 @@ namespace Disco.Services.Interop.ActiveDirectory public string Email { get; private set; } public string Phone { get; private set; } + public ADUserAccountControlFlags UserAccountControl { get; private set; } + + public bool IsCriticalSystemObject { get; private set; } + public List Groups { get; private set; } public Dictionary LoadedProperties { get; private set; } + public bool IsDisabled { get { return UserAccountControl.HasFlag(ADUserAccountControlFlags.ADS_UF_ACCOUNTDISABLE); } } + public bool IsLockedOut { get { return UserAccountControl.HasFlag(ADUserAccountControlFlags.ADS_UF_LOCKOUT); } } + public bool IsPasswordExpired { get { return UserAccountControl.HasFlag(ADUserAccountControlFlags.ADS_UF_PASSWORD_EXPIRED); } } + private ADUserAccount(ADDomain Domain, string DistinguishedName, SecurityIdentifier SecurityIdentifier, string SamAccountName, - string Name, string DisplayName, string Surname, string GivenName, string Email, string Phone, - List Groups, Dictionary LoadedProperties) + string Name, string DisplayName, string Surname, string GivenName, string Email, string Phone, ADUserAccountControlFlags UserAccountControl, + bool IsCriticalSystemObject, List Groups, Dictionary LoadedProperties) { this.Domain = Domain; this.DistinguishedName = DistinguishedName; @@ -48,6 +56,8 @@ namespace Disco.Services.Interop.ActiveDirectory this.GivenName = GivenName; this.Email = Email; this.Phone = Phone; + this.UserAccountControl = UserAccountControl; + this.IsCriticalSystemObject = IsCriticalSystemObject; this.Groups = Groups; this.LoadedProperties = LoadedProperties; } @@ -57,22 +67,25 @@ namespace Disco.Services.Interop.ActiveDirectory if (SearchResult == null) throw new ArgumentNullException("SearchResult"); - string name = SearchResult.Value("name"); - string sAMAccountName = SearchResult.Value("sAMAccountName"); - string distinguishedName = SearchResult.Value("distinguishedName"); + var name = SearchResult.Value("name"); + var sAMAccountName = SearchResult.Value("sAMAccountName"); + var distinguishedName = SearchResult.Value("distinguishedName"); var objectSid = new SecurityIdentifier(SearchResult.Value("objectSid"), 0); var displayName = SearchResult.Value("displayName") ?? sAMAccountName; var surname = SearchResult.Value("sn"); - string givenName = SearchResult.Value("givenName"); - string email = SearchResult.Value("mail"); - string phone = SearchResult.Value("telephoneNumber"); + var givenName = SearchResult.Value("givenName"); + var email = SearchResult.Value("mail"); + var phone = SearchResult.Value("telephoneNumber"); + + var userAccountControl = (ADUserAccountControlFlags)SearchResult.Value("userAccountControl"); + var isCriticalSystemObject = SearchResult.Value("isCriticalSystemObject"); List groups = null; // Don't load Groups when doing a quick search if (!Quick) { - int primaryGroupID = (int)SearchResult.Value("primaryGroupID"); + var primaryGroupID = SearchResult.Value("primaryGroupID"); var primaryGroupSid = ADHelpers.BuildPrimaryGroupSid(objectSid, primaryGroupID); var memberGroups = SearchResult.Values("memberOf"); @@ -107,6 +120,8 @@ namespace Disco.Services.Interop.ActiveDirectory givenName, email, phone, + userAccountControl, + isCriticalSystemObject, groups, additionalProperties); } @@ -125,24 +140,26 @@ namespace Disco.Services.Interop.ActiveDirectory switch (PropertyName.ToLower()) { case "name": - return new string[] { this.Name }.OfType(); + return new string[] { Name }.OfType(); case "samaccountname": - return new string[] { this.SamAccountName }.OfType(); + return new string[] { SamAccountName }.OfType(); case "distinguishedname": - return new string[] { this.DistinguishedName }.OfType(); + return new string[] { DistinguishedName }.OfType(); case "objectsid": - return new SecurityIdentifier[] { this.SecurityIdentifier }.OfType(); + return new SecurityIdentifier[] { SecurityIdentifier }.OfType(); case "sn": - return new string[] { this.Surname }.OfType(); + return new string[] { Surname }.OfType(); case "givenname": - return new string[] { this.GivenName }.OfType(); + return new string[] { GivenName }.OfType(); case "mail": - return new string[] { this.Email }.OfType(); + return new string[] { Email }.OfType(); case "telephonenumber": - return new string[] { this.Phone }.OfType(); + return new string[] { Phone }.OfType(); + case "userAccountControl": + return new int[] { (int)UserAccountControl }.OfType(); default: object[] adProperty; - if (this.LoadedProperties.TryGetValue(PropertyName, out adProperty)) + if (LoadedProperties.TryGetValue(PropertyName, out adProperty)) return adProperty.OfType(); else return Enumerable.Empty(); @@ -153,18 +170,261 @@ namespace Disco.Services.Interop.ActiveDirectory { return new User { - UserId = this.Id, - DisplayName = this.DisplayName, - Surname = this.Surname, - GivenName = this.GivenName, - EmailAddress = this.Email, - PhoneNumber = this.Phone, + UserId = Id, + DisplayName = DisplayName, + Surname = Surname, + GivenName = GivenName, + EmailAddress = Email, + PhoneNumber = Phone, }; } + #region Actions + + public void DeleteAccount(ADDomainController WritableDomainController) + { + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account [{0}] is a Critical System Active Directory Object and Disco ICT refuses to modify it", DistinguishedName)); + + if (!WritableDomainController.IsWritable) + throw new InvalidOperationException(string.Format("The domain controller [{0}] is not writable. This action (Delete Account) requires a writable domain controller.", Name)); + + using (ADDirectoryEntry entry = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) + { + entry.Entry.DeleteTree(); + } + } + public void DeleteAccount() + { + DeleteAccount(Domain.GetAvailableDomainController(RequireWritable: true)); + } + + public void DisableAccount(ADDomainController WritableDomainController) + { + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco ICT refuses to modify it", DistinguishedName)); + + if (!IsDisabled) + { + using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) + { + var accountControl = (ADUserAccountControlFlags)deAccount.Entry.Properties["userAccountControl"][0]; + if (!accountControl.HasFlag(ADUserAccountControlFlags.ADS_UF_ACCOUNTDISABLE)) + { + var updatedAccountControl = (accountControl | ADUserAccountControlFlags.ADS_UF_ACCOUNTDISABLE); + deAccount.Entry.Properties["userAccountControl"][0] = (int)updatedAccountControl; + deAccount.Entry.CommitChanges(); + UserAccountControl = updatedAccountControl; + } + } + } + } + public void DisableAccount() + { + if (!IsDisabled) + { + DisableAccount(Domain.GetAvailableDomainController(RequireWritable: true)); + } + } + + public void EnableAccount(ADDomainController WritableDomainController) + { + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", DistinguishedName)); + + if (IsDisabled) + { + using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) + { + var accountControl = (ADUserAccountControlFlags)deAccount.Entry.Properties["userAccountControl"][0]; + if (accountControl.HasFlag(ADUserAccountControlFlags.ADS_UF_ACCOUNTDISABLE)) + { + var updatedAccountControl = (accountControl ^ ADUserAccountControlFlags.ADS_UF_ACCOUNTDISABLE); + deAccount.Entry.Properties["userAccountControl"][0] = (int)updatedAccountControl; + deAccount.Entry.CommitChanges(); + UserAccountControl = updatedAccountControl; + } + } + } + } + public void EnableAccount() + { + if (IsDisabled) + { + EnableAccount(Domain.GetAvailableDomainController(RequireWritable: true)); + } + } + + public void SetDisplayName(ADDomainController WritableDomainController, string DisplayName) + { + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", DistinguishedName)); + + if (this.DisplayName != DisplayName) + { + using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) + { + var property = deAccount.Entry.Properties["displayName"]; + if (property.Count != 1 || (property[0] as string) != DisplayName) + { + if (property.Count > 0) + { + property.Clear(); + } + if (!string.IsNullOrEmpty(DisplayName)) + { + property.Add(DisplayName); + } + deAccount.Entry.CommitChanges(); + } + } + } + } + public void SetDisplayName(string DisplayName) + { + if (this.DisplayName != DisplayName) + { + SetDisplayName(Domain.GetAvailableDomainController(RequireWritable: true), DisplayName); + } + } + + public void SetSurname(ADDomainController WritableDomainController, string Surname) + { + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", DistinguishedName)); + + if (this.Surname != Surname) + { + using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) + { + var property = deAccount.Entry.Properties["sn"]; + if (property.Count != 1 || (property[0] as string) != Surname) + { + if (property.Count > 0) + { + property.Clear(); + } + if (!string.IsNullOrEmpty(Surname)) + { + property.Add(Surname); + } + deAccount.Entry.CommitChanges(); + } + } + } + } + public void SetSurname(string Surname) + { + if (this.Surname != Surname) + { + SetSurname(Domain.GetAvailableDomainController(RequireWritable: true), Surname); + } + } + + public void SetGivenName(ADDomainController WritableDomainController, string GivenName) + { + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", DistinguishedName)); + + if (this.GivenName != GivenName) + { + using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) + { + var property = deAccount.Entry.Properties["givenName"]; + if (property.Count != 1 || (property[0] as string) != GivenName) + { + if (property.Count > 0) + { + property.Clear(); + } + if (!string.IsNullOrEmpty(GivenName)) + { + property.Add(GivenName); + } + deAccount.Entry.CommitChanges(); + } + } + } + } + public void SetGivenName(string GivenName) + { + if (this.GivenName != GivenName) + { + SetGivenName(Domain.GetAvailableDomainController(RequireWritable: true), GivenName); + } + } + + public void SetEmail(ADDomainController WritableDomainController, string Email) + { + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", DistinguishedName)); + + if (this.Email != Email) + { + using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) + { + var property = deAccount.Entry.Properties["mail"]; + if (property.Count != 1 || (property[0] as string) != Email) + { + if (property.Count > 0) + { + property.Clear(); + } + if (!string.IsNullOrEmpty(Email)) + { + property.Add(Email); + } + deAccount.Entry.CommitChanges(); + } + } + } + } + public void SetEmail(string Email) + { + if (this.Email != Email) + { + SetEmail(Domain.GetAvailableDomainController(RequireWritable: true), Email); + } + } + + public void SetPhone(ADDomainController WritableDomainController, string Phone) + { + if (IsCriticalSystemObject) + throw new InvalidOperationException(string.Format("This account {0} is a Critical System Active Directory Object and Disco refuses to modify it", DistinguishedName)); + + if (this.Phone != Phone) + { + using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(DistinguishedName)) + { + var property = deAccount.Entry.Properties["telephoneNumber"]; + if (property.Count != 1 || (property[0] as string) != Phone) + { + if (property.Count > 0) + { + property.Clear(); + } + if (!string.IsNullOrEmpty(Phone)) + { + property.Add(Phone); + } + deAccount.Entry.CommitChanges(); + } + } + } + } + public void SetPhone(string Phone) + { + if (this.Phone != Phone) + { + SetPhone(Domain.GetAvailableDomainController(RequireWritable: true), Phone); + } + } + + #endregion + public override string ToString() { - return this.Id; + return Id; } public override bool Equals(object obj) @@ -172,11 +432,11 @@ namespace Disco.Services.Interop.ActiveDirectory if (obj == null || !(obj is ADUserAccount)) return false; else - return this.DistinguishedName == ((ADUserAccount)obj).DistinguishedName; + return DistinguishedName == ((ADUserAccount)obj).DistinguishedName; } public override int GetHashCode() { - return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(this.DistinguishedName); + return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(DistinguishedName); } } } diff --git a/Disco.Services/Interop/ActiveDirectory/ADUserAccountControlFlags.cs b/Disco.Services/Interop/ActiveDirectory/ADUserAccountControlFlags.cs new file mode 100644 index 00000000..fe735c29 --- /dev/null +++ b/Disco.Services/Interop/ActiveDirectory/ADUserAccountControlFlags.cs @@ -0,0 +1,93 @@ +using System; + +namespace Disco.Services.Interop.ActiveDirectory +{ + [Flags] + public enum ADUserAccountControlFlags : int + { + /// + /// The logon script is executed. + /// + ADS_UF_SCRIPT = 0x00000001, + /// + /// The user account is disabled. + /// + ADS_UF_ACCOUNTDISABLE = 0x00000002, + /// + /// The home directory is required. + /// + ADS_UF_HOMEDIR_REQUIRED = 0x00000008, + /// + /// The account is currently locked out. + /// + ADS_UF_LOCKOUT = 0x00000010, + /// + /// No password is required. + /// + ADS_UF_PASSWD_NOTREQD = 0x00000020, + /// + /// The user cannot change the password. Note: You cannot assign the permission settings of PASSWD_CANT_CHANGE by directly modifying the UserAccountControl attribute. + /// + ADS_UF_PASSWD_CANT_CHANGE = 0x00000040, + /// + /// The user can send an encrypted password. + /// + ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0x00000080, + /// + /// This is an account for users whose primary account is in another domain. This account provides user access to this domain, but not to any domain that trusts this domain. Also known as a local user account. + /// + ADS_UF_TEMP_DUPLICATE_ACCOUNT = 0x00000100, + /// + /// This is a default account type that represents a typical user. + /// + ADS_UF_NORMAL_ACCOUNT = 0x00000200, + /// + /// This is a permit to trust account for a system domain that trusts other domains. + /// + ADS_UF_INTERDOMAIN_TRUST_ACCOUNT = 0x00000800, + /// + /// This is a computer account for a computer that is a member of this domain. + /// + ADS_UF_WORKSTATION_TRUST_ACCOUNT = 0x00001000, + /// + /// This is a computer account for a system backup domain controller that is a member of this domain. + /// + ADS_UF_SERVER_TRUST_ACCOUNT = 0x00002000, + /// + /// The password for this account will never expire. + /// + ADS_UF_DONT_EXPIRE_PASSWD = 0x00010000, + /// + /// This is an MNS logon account. + /// + ADS_UF_MNS_LOGON_ACCOUNT = 0x00020000, + /// + /// The user must log on using a smart card. + /// + ADS_UF_SMARTCARD_REQUIRED = 0x00040000, + /// + /// The service account (user or computer account), under which a service runs, is trusted for Kerberos delegation. Any such service can impersonate a client requesting the service. + /// + ADS_UF_TRUSTED_FOR_DELEGATION = 0x00080000, + /// + /// The security context of the user will not be delegated to a service even if the service account is set as trusted for Kerberos delegation. + /// + ADS_UF_NOT_DELEGATED = 0x00100000, + /// + /// Restrict this principal to use only Data Encryption Standard (DES) encryption types for keys. + /// + ADS_UF_USE_DES_KEY_ONLY = 0x00200000, + /// + /// This account does not require Kerberos pre-authentication for logon. + /// + ADS_UF_DONT_REQUIRE_PREAUTH = 0x00400000, + /// + /// The user password has expired. This flag is created by the system using data from the Pwd-Last-Set attribute and the domain policy. + /// + ADS_UF_PASSWORD_EXPIRED = 0x00800000, + /// + /// The account is enabled for delegation. This is a security-sensitive setting; accounts with this option enabled should be strictly controlled. This setting enables a service running under the account to assume a client identity and authenticate as that user to other remote servers on the network. + /// + ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x01000000 + } +} diff --git a/Disco.Services/Interop/ActiveDirectory/ActiveDirectory.cs b/Disco.Services/Interop/ActiveDirectory/ActiveDirectory.cs index f4e0f3c2..fb27581b 100644 --- a/Disco.Services/Interop/ActiveDirectory/ActiveDirectory.cs +++ b/Disco.Services/Interop/ActiveDirectory/ActiveDirectory.cs @@ -1,4 +1,5 @@ using Disco.Data.Repository; +using Disco.Models.Repository; using System; using System.Collections.Generic; using System.Diagnostics; @@ -70,6 +71,12 @@ namespace Disco.Services.Interop.ActiveDirectory 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 = ActiveDirectory.DefaultSearchResultLimit, params string[] AdditionalProperties) { if (string.IsNullOrWhiteSpace(Term))