509 lines
22 KiB
C#
509 lines
22 KiB
C#
using Disco.Models.Repository;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Security.Principal;
|
|
|
|
namespace Disco.Services.Interop.ActiveDirectory
|
|
{
|
|
public class ADUserAccount : IADObject
|
|
{
|
|
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", "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; }
|
|
|
|
public string DistinguishedName { get; private set; }
|
|
public SecurityIdentifier SecurityIdentifier { get; private set; }
|
|
|
|
public string Id { get { return string.Format(@"{0}\{1}", Domain.NetBiosName, SamAccountName); } }
|
|
public string SamAccountName { get; private set; }
|
|
|
|
public string Name { get; private set; }
|
|
public string DisplayName { get; private set; }
|
|
|
|
public string Surname { get; private set; }
|
|
public string GivenName { get; private set; }
|
|
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<ADGroup> Groups { get; private set; }
|
|
|
|
public Dictionary<string, object[]> 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, ADUserAccountControlFlags UserAccountControl,
|
|
bool IsCriticalSystemObject, List<ADGroup> Groups, Dictionary<string, object[]> LoadedProperties)
|
|
{
|
|
this.Domain = Domain;
|
|
this.DistinguishedName = DistinguishedName;
|
|
this.SecurityIdentifier = SecurityIdentifier;
|
|
this.SamAccountName = SamAccountName;
|
|
this.Name = Name;
|
|
this.DisplayName = DisplayName;
|
|
this.Surname = Surname;
|
|
this.GivenName = GivenName;
|
|
this.Email = Email;
|
|
this.Phone = Phone;
|
|
this.UserAccountControl = UserAccountControl;
|
|
this.IsCriticalSystemObject = IsCriticalSystemObject;
|
|
this.Groups = Groups;
|
|
this.LoadedProperties = LoadedProperties;
|
|
}
|
|
|
|
public static ADUserAccount FromSearchResult(ADSearchResult SearchResult, bool Quick, string[] AdditionalProperties)
|
|
{
|
|
if (SearchResult == null)
|
|
throw new ArgumentNullException("SearchResult");
|
|
|
|
var name = SearchResult.Value<string>("name");
|
|
var sAMAccountName = SearchResult.Value<string>("sAMAccountName");
|
|
var distinguishedName = SearchResult.Value<string>("distinguishedName");
|
|
var objectSid = new SecurityIdentifier(SearchResult.Value<byte[]>("objectSid"), 0);
|
|
|
|
var displayName = SearchResult.Value<string>("displayName") ?? sAMAccountName;
|
|
var surname = SearchResult.Value<string>("sn");
|
|
var givenName = SearchResult.Value<string>("givenName");
|
|
var email = SearchResult.Value<string>("mail");
|
|
var phone = SearchResult.Value<string>("telephoneNumber");
|
|
|
|
var userAccountControl = (ADUserAccountControlFlags)SearchResult.Value<int>("userAccountControl");
|
|
var isCriticalSystemObject = SearchResult.Value<bool>("isCriticalSystemObject");
|
|
|
|
List<ADGroup> groups = null;
|
|
// Don't load Groups when doing a quick search
|
|
if (!Quick)
|
|
{
|
|
var primaryGroupID = SearchResult.Value<int>("primaryGroupID");
|
|
var primaryGroupSid = ADHelpers.BuildPrimaryGroupSid(objectSid, primaryGroupID);
|
|
var memberGroups = SearchResult.Values<string>("memberOf");
|
|
|
|
var primaryGroup = ActiveDirectory.GroupCache.GetGroup(primaryGroupSid);
|
|
|
|
var groupDistinguishedNames =
|
|
new string[] { primaryGroup.DistinguishedName }
|
|
.Concat(memberGroups);
|
|
|
|
groups = ActiveDirectory.GroupCache.GetRecursiveGroups(groupDistinguishedNames).ToList();
|
|
}
|
|
|
|
// Additional Properties
|
|
Dictionary<string, object[]> additionalProperties;
|
|
if (AdditionalProperties != null)
|
|
additionalProperties = AdditionalProperties
|
|
.Select(p => Tuple.Create(p, SearchResult.Values<object>(p).ToArray()))
|
|
.ToDictionary(t => t.Item1, t => t.Item2);
|
|
else
|
|
{
|
|
additionalProperties = new Dictionary<string, object[]>();
|
|
}
|
|
|
|
return new ADUserAccount(
|
|
SearchResult.Domain,
|
|
distinguishedName,
|
|
objectSid,
|
|
sAMAccountName,
|
|
name,
|
|
displayName,
|
|
surname,
|
|
givenName,
|
|
email,
|
|
phone,
|
|
userAccountControl,
|
|
isCriticalSystemObject,
|
|
groups,
|
|
additionalProperties);
|
|
}
|
|
|
|
public static ADUserAccount FromDirectoryEntry(ADDirectoryEntry directoryEntry, bool quick, string[] additionalProperties)
|
|
{
|
|
if (directoryEntry == null)
|
|
throw new ArgumentNullException(nameof(directoryEntry));
|
|
|
|
var properties = directoryEntry.Entry.Properties;
|
|
|
|
var name = properties.Value<string>("name");
|
|
var sAMAccountName = properties.Value<string>("sAMAccountName");
|
|
var distinguishedName = properties.Value<string>("distinguishedName");
|
|
var objectSid = new SecurityIdentifier(properties.Value<byte[]>("objectSid"), 0);
|
|
|
|
var displayName = properties.Value<string>("displayName") ?? sAMAccountName;
|
|
var surname = properties.Value<string>("sn");
|
|
var givenName = properties.Value<string>("givenName");
|
|
var email = properties.Value<string>("mail");
|
|
var phone = properties.Value<string>("telephoneNumber");
|
|
|
|
var userAccountControl = (ADUserAccountControlFlags)properties.Value<int>("userAccountControl");
|
|
var isCriticalSystemObject = properties.Value<bool>("isCriticalSystemObject");
|
|
|
|
List<ADGroup> groups = null;
|
|
// Don't load Groups when doing a quick search
|
|
if (!quick)
|
|
{
|
|
var primaryGroupID = properties.Value<int>("primaryGroupID");
|
|
var primaryGroupSid = ADHelpers.BuildPrimaryGroupSid(objectSid, primaryGroupID);
|
|
var memberGroups = properties.Values<string>("memberOf");
|
|
|
|
var primaryGroup = ActiveDirectory.GroupCache.GetGroup(primaryGroupSid);
|
|
|
|
var groupDistinguishedNames =
|
|
new string[] { primaryGroup.DistinguishedName }
|
|
.Concat(memberGroups);
|
|
|
|
groups = ActiveDirectory.GroupCache.GetRecursiveGroups(groupDistinguishedNames).ToList();
|
|
}
|
|
|
|
// Additional Properties
|
|
Dictionary<string, object[]> additionalProps;
|
|
if (additionalProperties != null)
|
|
additionalProps = additionalProperties
|
|
.Select(p => Tuple.Create(p, properties.Values<object>(p).ToArray()))
|
|
.ToDictionary(t => t.Item1, t => t.Item2);
|
|
else
|
|
{
|
|
additionalProps = new Dictionary<string, object[]>();
|
|
}
|
|
|
|
return new ADUserAccount(
|
|
directoryEntry.Domain,
|
|
distinguishedName,
|
|
objectSid,
|
|
sAMAccountName,
|
|
name,
|
|
displayName,
|
|
surname,
|
|
givenName,
|
|
email,
|
|
phone,
|
|
userAccountControl,
|
|
isCriticalSystemObject,
|
|
groups,
|
|
additionalProps);
|
|
}
|
|
|
|
[Obsolete("Use generic equivalents: GetPropertyValue<T>(string PropertyName)")]
|
|
public object GetPropertyValue(string PropertyName, int Index = 0)
|
|
{
|
|
return GetPropertyValues<object>(PropertyName).Skip(Index).FirstOrDefault();
|
|
}
|
|
public T GetPropertyValue<T>(string PropertyName)
|
|
{
|
|
return GetPropertyValues<T>(PropertyName).FirstOrDefault();
|
|
}
|
|
public IEnumerable<T> GetPropertyValues<T>(string PropertyName)
|
|
{
|
|
switch (PropertyName.ToLower())
|
|
{
|
|
case "name":
|
|
return new string[] { Name }.OfType<T>();
|
|
case "samaccountname":
|
|
return new string[] { SamAccountName }.OfType<T>();
|
|
case "distinguishedname":
|
|
return new string[] { DistinguishedName }.OfType<T>();
|
|
case "objectsid":
|
|
return new SecurityIdentifier[] { SecurityIdentifier }.OfType<T>();
|
|
case "sn":
|
|
return new string[] { Surname }.OfType<T>();
|
|
case "givenname":
|
|
return new string[] { GivenName }.OfType<T>();
|
|
case "mail":
|
|
return new string[] { Email }.OfType<T>();
|
|
case "telephonenumber":
|
|
return new string[] { Phone }.OfType<T>();
|
|
case "userAccountControl":
|
|
return new int[] { (int)UserAccountControl }.OfType<T>();
|
|
default:
|
|
object[] adProperty;
|
|
if (LoadedProperties.TryGetValue(PropertyName, out adProperty))
|
|
return adProperty.OfType<T>();
|
|
else
|
|
return Enumerable.Empty<T>();
|
|
}
|
|
}
|
|
|
|
public User ToRepositoryUser()
|
|
{
|
|
return new User
|
|
{
|
|
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 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 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 ICT 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 ICT 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 ICT 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 ICT 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 ICT 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 Id;
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
if (obj == null || !(obj is ADUserAccount))
|
|
return false;
|
|
else
|
|
return DistinguishedName == ((ADUserAccount)obj).DistinguishedName;
|
|
}
|
|
public override int GetHashCode()
|
|
{
|
|
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(DistinguishedName);
|
|
}
|
|
}
|
|
}
|