Fix #16: Include Primary Group for AD Users
Caches group SIDs and retires unmanaged SID transform PInvoke for managed equivalents
This commit is contained in:
@@ -69,7 +69,7 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
string name = result.Properties["name"][0].ToString();
|
string name = result.Properties["name"][0].ToString();
|
||||||
string sAMAccountName = result.Properties["sAMAccountName"][0].ToString();
|
string sAMAccountName = result.Properties["sAMAccountName"][0].ToString();
|
||||||
string distinguishedName = result.Properties["distinguishedName"][0].ToString();
|
string distinguishedName = result.Properties["distinguishedName"][0].ToString();
|
||||||
string objectSid = ActiveDirectoryHelpers.ConvertBytesToSIDString((byte[])result.Properties["objectSid"][0]);
|
string objectSid = ActiveDirectoryHelpers.ConvertBytesToSDDLString((byte[])result.Properties["objectSid"][0]);
|
||||||
|
|
||||||
var dNSNameProperty = result.Properties["dNSHostName"];
|
var dNSNameProperty = result.Properties["dNSHostName"];
|
||||||
string dNSName = null;
|
string dNSName = null;
|
||||||
@@ -117,7 +117,8 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
string name = result.Properties["name"][0].ToString();
|
string name = result.Properties["name"][0].ToString();
|
||||||
string username = result.Properties["sAMAccountName"][0].ToString();
|
string username = result.Properties["sAMAccountName"][0].ToString();
|
||||||
string distinguishedName = result.Properties["distinguishedName"][0].ToString();
|
string distinguishedName = result.Properties["distinguishedName"][0].ToString();
|
||||||
string objectSid = ActiveDirectoryHelpers.ConvertBytesToSIDString((byte[])result.Properties["objectSid"][0]);
|
byte[] objectSid = (byte[])result.Properties["objectSid"][0];
|
||||||
|
string objectSidSDDL = ActiveDirectoryHelpers.ConvertBytesToSDDLString(objectSid);
|
||||||
|
|
||||||
ResultPropertyValueCollection displayNameProp = result.Properties["displayName"];
|
ResultPropertyValueCollection displayNameProp = result.Properties["displayName"];
|
||||||
string displayName = username;
|
string displayName = username;
|
||||||
@@ -140,7 +141,10 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
if (phoneProp.Count > 0)
|
if (phoneProp.Count > 0)
|
||||||
phone = phoneProp[0].ToString();
|
phone = phoneProp[0].ToString();
|
||||||
|
|
||||||
IEnumerable<string> groupCNs = result.Properties["memberOf"].Cast<string>();
|
int primaryGroupID = (int)result.Properties["primaryGroupID"][0];
|
||||||
|
string primaryGroupSid = ActiveDirectoryHelpers.ConvertBytesToSDDLString(ActiveDirectoryHelpers.BuildPrimaryGroupSid(objectSid, primaryGroupID));
|
||||||
|
var groupCNs = result.Properties["memberOf"].Cast<string>().ToList();
|
||||||
|
groupCNs.Add(ActiveDirectoryCachedGroups.GetGroupsCnForSid(primaryGroupSid));
|
||||||
List<string> groups = ActiveDirectoryCachedGroups.GetGroups(groupCNs).Select(g => g.ToLower()).ToList();
|
List<string> groups = ActiveDirectoryCachedGroups.GetGroups(groupCNs).Select(g => g.ToLower()).ToList();
|
||||||
|
|
||||||
//foreach (string groupCN in result.Properties["memberOf"])
|
//foreach (string groupCN in result.Properties["memberOf"])
|
||||||
@@ -194,7 +198,7 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
DistinguishedName = distinguishedName,
|
DistinguishedName = distinguishedName,
|
||||||
sAMAccountName = username,
|
sAMAccountName = username,
|
||||||
DisplayName = displayName,
|
DisplayName = displayName,
|
||||||
ObjectSid = objectSid,
|
ObjectSid = objectSidSDDL,
|
||||||
Type = type,
|
Type = type,
|
||||||
Path = result.Path,
|
Path = result.Path,
|
||||||
LoadedProperties = additionalProperties
|
LoadedProperties = additionalProperties
|
||||||
@@ -219,7 +223,8 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
"sn",
|
"sn",
|
||||||
"givenName",
|
"givenName",
|
||||||
"memberOf",
|
"memberOf",
|
||||||
"mail",
|
"primaryGroupID",
|
||||||
|
"mail",
|
||||||
"telephoneNumber"
|
"telephoneNumber"
|
||||||
};
|
};
|
||||||
loadProperties.AddRange(AdditionalProperties);
|
loadProperties.AddRange(AdditionalProperties);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
{
|
{
|
||||||
public class ActiveDirectoryCachedGroups : ScheduledTask
|
public class ActiveDirectoryCachedGroups : ScheduledTask
|
||||||
{
|
{
|
||||||
|
private static ConcurrentDictionary<string, Tuple<ADCachedGroup, DateTime>> _SidCache = new ConcurrentDictionary<string, Tuple<ADCachedGroup, DateTime>>();
|
||||||
private static ConcurrentDictionary<string, Tuple<ADCachedGroup, DateTime>> _Cache = new ConcurrentDictionary<string, Tuple<ADCachedGroup, DateTime>>();
|
private static ConcurrentDictionary<string, Tuple<ADCachedGroup, DateTime>> _Cache = new ConcurrentDictionary<string, Tuple<ADCachedGroup, DateTime>>();
|
||||||
private const long CacheTimeoutTicks = 6000000000; // 10 Minutes
|
private const long CacheTimeoutTicks = 6000000000; // 10 Minutes
|
||||||
|
|
||||||
@@ -33,6 +34,14 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
foreach (var group in GetGroupsRecursive(GroupCN, new Stack<ADCachedGroup>()))
|
foreach (var group in GetGroupsRecursive(GroupCN, new Stack<ADCachedGroup>()))
|
||||||
yield return group.FriendlyName;
|
yield return group.FriendlyName;
|
||||||
}
|
}
|
||||||
|
public static string GetGroupsCnForSid(string GroupSid)
|
||||||
|
{
|
||||||
|
var sidGroup = GetGroupBySid(GroupSid);
|
||||||
|
if (sidGroup == null)
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
return sidGroup.CN;
|
||||||
|
}
|
||||||
private static IEnumerable<ADCachedGroup> GetGroupsRecursive(string GroupCN, Stack<ADCachedGroup> RecursiveTree)
|
private static IEnumerable<ADCachedGroup> GetGroupsRecursive(string GroupCN, Stack<ADCachedGroup> RecursiveTree)
|
||||||
{
|
{
|
||||||
var group = GetGroup(GroupCN);
|
var group = GetGroup(GroupCN);
|
||||||
@@ -62,8 +71,27 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
if (groupRecord == null)
|
if (groupRecord == null)
|
||||||
{
|
{
|
||||||
// Load from AD
|
// Load from AD
|
||||||
var group = ADCachedGroup.LoadFromAD(GroupCN);
|
var group = ADCachedGroup.LoadWithCN(GroupCN);
|
||||||
SetValue(GroupCN, group);
|
SetValue(group);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Return from Cache
|
||||||
|
return groupRecord.Item1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static ADCachedGroup GetGroupBySid(string GroupSid)
|
||||||
|
{
|
||||||
|
// Check Cache
|
||||||
|
Tuple<ADCachedGroup, DateTime> groupRecord = TrySidCache(GroupSid);
|
||||||
|
|
||||||
|
if (groupRecord == null)
|
||||||
|
{
|
||||||
|
// Load from AD
|
||||||
|
var group = ADCachedGroup.LoadWithSid(GroupSid);
|
||||||
|
SetValue(group);
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
@@ -83,33 +111,70 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
if (groupRecord.Item2 > DateTime.Now)
|
if (groupRecord.Item2 > DateTime.Now)
|
||||||
return groupRecord;
|
return groupRecord;
|
||||||
else
|
else
|
||||||
_Cache.TryRemove(groupCN, out groupRecord);
|
{
|
||||||
|
if (_Cache.TryRemove(groupCN, out groupRecord))
|
||||||
|
_SidCache.TryRemove(groupRecord.Item1.ObjectSid, out groupRecord);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
private static bool SetValue(string GroupCN, ADCachedGroup GroupRecord)
|
private static Tuple<ADCachedGroup, DateTime> TrySidCache(string GroupSid)
|
||||||
{
|
{
|
||||||
string key = GroupCN.ToLower();
|
Tuple<ADCachedGroup, DateTime> groupRecord;
|
||||||
Tuple<ADCachedGroup, DateTime> groupRecord = new Tuple<ADCachedGroup, DateTime>(GroupRecord, DateTime.Now.AddTicks(CacheTimeoutTicks));
|
if (_SidCache.TryGetValue(GroupSid, out groupRecord))
|
||||||
if (_Cache.ContainsKey(key))
|
|
||||||
{
|
{
|
||||||
Tuple<ADCachedGroup, DateTime> oldGroupRecord;
|
if (groupRecord.Item2 > DateTime.Now)
|
||||||
if (_Cache.TryGetValue(key, out oldGroupRecord))
|
return groupRecord;
|
||||||
|
else
|
||||||
{
|
{
|
||||||
return _Cache.TryUpdate(key, groupRecord, oldGroupRecord);
|
if (_SidCache.TryRemove(GroupSid, out groupRecord))
|
||||||
|
_Cache.TryRemove(groupRecord.Item1.CN.ToLower(), out groupRecord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _Cache.TryAdd(key, groupRecord);
|
return null;
|
||||||
|
}
|
||||||
|
private static bool SetValue(ADCachedGroup GroupRecord)
|
||||||
|
{
|
||||||
|
Tuple<ADCachedGroup, DateTime> groupRecord = new Tuple<ADCachedGroup, DateTime>(GroupRecord, DateTime.Now.AddTicks(CacheTimeoutTicks));
|
||||||
|
Tuple<ADCachedGroup, DateTime> oldGroupRecord;
|
||||||
|
|
||||||
|
string key = GroupRecord.CN.ToLower();
|
||||||
|
if (_Cache.ContainsKey(key))
|
||||||
|
{
|
||||||
|
if (_Cache.TryGetValue(key, out oldGroupRecord))
|
||||||
|
{
|
||||||
|
_Cache.TryUpdate(key, groupRecord, oldGroupRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_Cache.TryAdd(key, groupRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
string sid = GroupRecord.ObjectSid;
|
||||||
|
if (_SidCache.ContainsKey(sid))
|
||||||
|
{
|
||||||
|
if (_SidCache.TryGetValue(sid, out oldGroupRecord))
|
||||||
|
{
|
||||||
|
_SidCache.TryUpdate(sid, groupRecord, oldGroupRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_SidCache.TryAdd(sid, groupRecord);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ADCachedGroup
|
private class ADCachedGroup
|
||||||
{
|
{
|
||||||
|
public string ObjectSid { get; set; }
|
||||||
public string CN { get; private set; }
|
public string CN { get; private set; }
|
||||||
public string FriendlyName { get; private set; }
|
public string FriendlyName { get; private set; }
|
||||||
|
|
||||||
public List<string> MemberOf { get; private set; }
|
public List<string> MemberOf { get; private set; }
|
||||||
|
|
||||||
public static ADCachedGroup LoadFromAD(string CN)
|
public static ADCachedGroup LoadWithCN(string CN)
|
||||||
{
|
{
|
||||||
ADCachedGroup group = null;
|
ADCachedGroup group = null;
|
||||||
|
|
||||||
@@ -122,6 +187,7 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
CN = CN
|
CN = CN
|
||||||
};
|
};
|
||||||
|
|
||||||
|
group.ObjectSid = ActiveDirectoryHelpers.ConvertBytesToSDDLString((byte[])groupDE.Properties["objectSid"].Value);
|
||||||
group.FriendlyName = (string)groupDE.Properties["sAMAccountName"].Value;
|
group.FriendlyName = (string)groupDE.Properties["sAMAccountName"].Value;
|
||||||
|
|
||||||
var groupMemberOf = groupDE.Properties["memberOf"];
|
var groupMemberOf = groupDE.Properties["memberOf"];
|
||||||
@@ -135,6 +201,46 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ADCachedGroup LoadWithSid(string Sid)
|
||||||
|
{
|
||||||
|
using (DirectoryEntry dRootEntry = ActiveDirectoryHelpers.DefaultLdapRoot)
|
||||||
|
{
|
||||||
|
var loadProperties = new List<string> {
|
||||||
|
"distinguishedName",
|
||||||
|
"objectSid",
|
||||||
|
"sAMAccountName",
|
||||||
|
"memberOf"
|
||||||
|
};
|
||||||
|
|
||||||
|
var sidBytes = ActiveDirectoryHelpers.ConvertSDDLStringToBytes(Sid);
|
||||||
|
var sidBinaryString = ActiveDirectoryHelpers.ConvertBytesToBinarySidString(sidBytes);
|
||||||
|
|
||||||
|
using (DirectorySearcher dSearcher = new DirectorySearcher(dRootEntry, string.Format("(&(objectClass=group)(objectSid={0}))", sidBinaryString), loadProperties.ToArray(), SearchScope.Subtree))
|
||||||
|
{
|
||||||
|
SearchResult dResult = dSearcher.FindOne();
|
||||||
|
if (dResult != null)
|
||||||
|
{
|
||||||
|
var group = new ADCachedGroup()
|
||||||
|
{
|
||||||
|
CN = (string)dResult.Properties["distinguishedName"][0],
|
||||||
|
ObjectSid = ActiveDirectoryHelpers.ConvertBytesToSDDLString((byte[])dResult.Properties["objectSid"][0]),
|
||||||
|
FriendlyName = (string)dResult.Properties["sAMAccountName"][0]
|
||||||
|
};
|
||||||
|
|
||||||
|
var groupMemberOf = dResult.Properties["memberOf"];
|
||||||
|
if (groupMemberOf != null && groupMemberOf.Count > 0)
|
||||||
|
{
|
||||||
|
group.MemberOf = groupMemberOf.Cast<string>().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ADCachedGroup()
|
private ADCachedGroup()
|
||||||
{
|
{
|
||||||
// Private Constructor
|
// Private Constructor
|
||||||
@@ -143,6 +249,7 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
|
|
||||||
private static void CleanStaleCache()
|
private static void CleanStaleCache()
|
||||||
{
|
{
|
||||||
|
// Clean Cache
|
||||||
var groupKeys = _Cache.Keys.ToArray();
|
var groupKeys = _Cache.Keys.ToArray();
|
||||||
foreach (string groupKey in groupKeys)
|
foreach (string groupKey in groupKeys)
|
||||||
{
|
{
|
||||||
@@ -150,7 +257,23 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
if (_Cache.TryGetValue(groupKey, out groupRecord))
|
if (_Cache.TryGetValue(groupKey, out groupRecord))
|
||||||
{
|
{
|
||||||
if (groupRecord.Item2 <= DateTime.Now)
|
if (groupRecord.Item2 <= DateTime.Now)
|
||||||
|
{
|
||||||
_Cache.TryRemove(groupKey, out groupRecord);
|
_Cache.TryRemove(groupKey, out groupRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean SID Cache
|
||||||
|
groupKeys = _SidCache.Keys.ToArray();
|
||||||
|
foreach (string groupKey in groupKeys)
|
||||||
|
{
|
||||||
|
Tuple<ADCachedGroup, DateTime> groupRecord;
|
||||||
|
if (_SidCache.TryGetValue(groupKey, out groupRecord))
|
||||||
|
{
|
||||||
|
if (groupRecord.Item2 <= DateTime.Now)
|
||||||
|
{
|
||||||
|
_SidCache.TryRemove(groupKey, out groupRecord);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.DirectoryServices;
|
|||||||
using System.DirectoryServices.ActiveDirectory;
|
using System.DirectoryServices.ActiveDirectory;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Principal;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
@@ -128,22 +129,50 @@ namespace Disco.BI.Interop.ActiveDirectory
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
[System.Runtime.InteropServices.DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
internal static string ConvertBytesToSDDLString(byte[] SID)
|
||||||
private static extern bool ConvertSidToStringSid(byte[] pSID, ref System.Text.StringBuilder ptrSid);
|
|
||||||
internal static string ConvertBytesToSIDString(byte[] SID)
|
|
||||||
{
|
{
|
||||||
System.Text.StringBuilder sidString = new System.Text.StringBuilder();
|
SecurityIdentifier sID = new SecurityIdentifier(SID, 0);
|
||||||
bool flag = ActiveDirectoryHelpers.ConvertSidToStringSid(SID, ref sidString);
|
|
||||||
string ConvertBytesToSIDString;
|
return sID.ToString();
|
||||||
if (flag)
|
}
|
||||||
|
|
||||||
|
internal static byte[] ConvertSDDLStringToBytes(string SidSsdlString)
|
||||||
|
{
|
||||||
|
SecurityIdentifier sID = new SecurityIdentifier(SidSsdlString);
|
||||||
|
|
||||||
|
var sidBytes = new byte[sID.BinaryLength];
|
||||||
|
|
||||||
|
sID.GetBinaryForm(sidBytes, 0);
|
||||||
|
|
||||||
|
return sidBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static byte[] BuildPrimaryGroupSid(byte[] UserSID, int PrimaryGroupId)
|
||||||
|
{
|
||||||
|
var groupSid = (byte[])UserSID.Clone();
|
||||||
|
|
||||||
|
int ridOffset = groupSid.Length - 4;
|
||||||
|
int groupId = PrimaryGroupId;
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
ConvertBytesToSIDString = sidString.ToString();
|
groupSid[ridOffset + i] = (byte)(groupId & 0xFF);
|
||||||
|
groupId >>= 8;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
return groupSid;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string ConvertBytesToBinarySidString(byte[] SID)
|
||||||
|
{
|
||||||
|
StringBuilder escapedSid = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var sidByte in SID)
|
||||||
{
|
{
|
||||||
ConvertBytesToSIDString = null;
|
escapedSid.Append('\\');
|
||||||
|
escapedSid.Append(sidByte.ToString("x2"));
|
||||||
}
|
}
|
||||||
return ConvertBytesToSIDString;
|
|
||||||
|
return escapedSid.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string EscapeLdapQuery(string query)
|
internal static string EscapeLdapQuery(string query)
|
||||||
|
|||||||
Reference in New Issue
Block a user