Po małym przeszukaniu internetu (głównieCodeProject i ExpertExchange) udało mi się w końcu zrobić logowanie do AD z wykorzystaniem grup (ale tylko tych jawnie wpisanych użytkowników, bez rozwijania grup zagnieżdżonych).
Główny kod programu:
//try autchenitcation
string[] groupsToCheck ={ "cwiczeniowcy", "Asystenci", "operatorzy", "wykladowcy" };
try
{
//bind to ad
DirectoryEntry de = new DirectoryEntry("LDAP://dc=pjwstk,dc=edu,dc=pl", TBLogin.Text, TBHaslo.Text);
DirectorySearcher mySearcher = new DirectorySearcher(de);
mySearcher.Filter = ("(&(ObjectCategory=Person)(ObjectClass=user)(SAMAccountName=" + TBLogin.Text + "))");
mySearcher.PropertiesToLoad.Add("givenName");
mySearcher.PropertiesToLoad.Add("sn");
mySearcher.PropertiesToLoad.Add("Path");
mySearcher.PropertiesToLoad.Add("primaryGroupId");
mySearcher.PropertiesToLoad.Add("objectSid");
SearchResult results = mySearcher.FindOne();
if (null != results)
{
uImie = results.Properties["givenName"][0].ToString();
uNazwisko = results.Properties["sn"][0].ToString();
//get user groups
ArrayList groups = new ArrayList();
DirectoryEntry obUser = new DirectoryEntry(results.Path);
object obGroups = obUser.Invoke("Groups");
foreach (object ob in (IEnumerable)obGroups)
{
DirectoryEntry obGpEntry = new DirectoryEntry(ob);
groups.Add(obGpEntry.Name);
}
//get primary group
int primaryGroupId = (int)results.Properties["PrimaryGroupID"][0];
byte[] userSid = results.Properties["objectSid"][0] as byte[];
byte[] primaryGroupSid = BuildPrimaryGroupSID(userSid, primaryGroupId);
string adsPath = String.Format("LDAP://<SID={0}>", BuildOctetString(primaryGroupSid));
DirectoryEntry objUser = new DirectoryEntry(adsPath, TBLogin.Text, TBHaslo.Text);
groups.Add((string)objUser.Properties["name"].Value);
//check if member is in group
bool isInGroup =false;
foreach (string stc in groupsToCheck)
if (groups.Contains(stc)) isInGroup = true;
if (isInGroup)
{
//Authenticated
}
else
{ LblError.Text = "Nie masz uprawnien do wypelnienienia ankiety";
}
}
else LblError.Text += "Blad Autoryzacji";
}
catch (Exception dsce)
{
LblError.Text = "Blad Autoryzacji";
}
// insertXML(uImie,uNazwisko);
Do tego potrzebujemy jeszcze funkcję do konwersji SIDów
private byte[] BuildPrimaryGroupSID(byte[] userSid, int primaryGroupId)
{
byte[] rid = BitConverter.GetBytes(primaryGroupId);
for (int i = 0; i < rid.Length; i++)
{
userSid.SetValue(rid[i], new long[] { userSid.Length - (rid.Length - i) });
}
return userSid;
}
private string BuildOctetString(byte[] bytes)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
sb.AppendFormat("{0}", bytes[i].ToString("X2"));
}
return sb.ToString();
}
I klasę, która produkuje SID
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Runtime.InteropServices;
using PSID = System.IntPtr;
using HLOCAL = System.IntPtr;
using BOOL = System.Int32;
using HANDLE = System.IntPtr;
using DWORD = System.UInt32;
/// <summary>
/// Summary description for Class1
/// </summary>
public class sid
{
[DllImport("Advapi32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Auto)]
public static extern BOOL ConvertSidToStringSid(PSID Sid, out IntPtr StringSid);
public unsafe static string UnsafeGetSidString(object sid)
{
string sidString = null;
IntPtr strPtr;
fixed (byte* psid = (byte[])sid)
{
IntPtr psidPtr = (IntPtr)psid;
BOOL rc = ConvertSidToStringSid(psidPtr, out strPtr);
Win32.CheckCall(rc);
try
{
sidString = Marshal.PtrToStringAuto(strPtr);
}
finally
{
Win32.LocalFree(strPtr);
}
}
return sidString;
}
}
public class Win32
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern HLOCAL LocalFree(HLOCAL hMem);
public static void CheckCall(bool funcResult)
{
if (!funcResult)
{
ThrowLastError();
}
}
public static void CheckCall(BOOL funcResult)
{
CheckCall(funcResult != 0);
}
public static void CheckCall(HANDLE funcResult)
{
CheckCall(!IsNullHandle(funcResult));
}
public static DWORD GetLastError()
{
return (DWORD)Marshal.GetLastWin32Error();
}
public static bool IsNullHandle(HANDLE ptr)
{
return (ptr == IntPtr.Zero);
}
public static void ThrowLastError()
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
}
Oczywiście w web.config w <system.web> musimy dodać obsługę funkcji typu unsafe:
<compilation>
<assemblies>
<add assembly="System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
</assemblies>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs" compilerOptions="/unsafe" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</compilers>
</compilation>