Reputation: 1269
in an .NET application, I'm trying to authenticate users by username and password a against windows users, local ones as well as domain users. I already tried this solution . My code to get the PrincipalContext looks the following:
protected static PrincipalContext TryCreatePrincipalContext(String domain)
{
var computerDomain = TryGetComputerDomain();
if (String.IsNullOrEmpty(domain) && String.IsNullOrEmpty(computerDomain))
return new PrincipalContext(ContextType.Machine);
else if (String.IsNullOrEmpty(domain))
return new PrincipalContext(ContextType.Domain, computerDomain);
else
return new PrincipalContext(ContextType.Domain, domain);
}
protected static String TryGetComputerDomain()
{
try
{
var domain = Domain.GetComputerDomain();
return domain.Name;
} catch
{
return null;
}
}
That works fine for local windows users users and for remote users in an ActiveDirectory. But if I try to run the authentication on a machine, that is joined to a non-ActiveDirectory Domain Master, eg. a Samba Server I get the following Exception:
System.DirectoryServices.AccountManagement.PrincipalServerDownException: Mit dem Server konnte keine Verbindung hergestellt werden. --->
System.DirectoryServices.Protocols.LdapException: Der LDAP-Server ist nicht verfügbar.
bei System.DirectoryServices.Protocols.LdapConnection.Connect()
bei System.DirectoryServices.Protocols.LdapConnection.SendRequestHelper(DirectoryRequest request, Int32& messageID)
bei System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request, TimeSpan requestTimeout)
bei System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request)
bei System.DirectoryServices.AccountManagement.PrincipalContext.ReadServerConfig(String serverName, ServerProperties& properties)
--- Ende der internen Ausnahmestapelüberwachung ---
bei System.DirectoryServices.AccountManagement.PrincipalContext.ReadServerConfig(String serverName, ServerProperties& properties)
bei System.DirectoryServices.AccountManagement.PrincipalContext.DoServerVerifyAndPropRetrieval()
bei System.DirectoryServices.AccountManagement.PrincipalContext..ctor(ContextType contextType, String name, String container, ContextOptions options, String userName, String password)
bei System.DirectoryServices.AccountManagement.PrincipalContext..ctor(ContextType contextType, String name)
bei DomainAuthTest.DomainAuthenticator.TryCreatePrincipalContext(String domain)
bei DomainAuthTest.DomainAuthenticator.Authenticate(String domainUser, String password)
bei DomainAuthTest.Program.Main(String[] args)
So it seems that the PrincipalContext tries to use LDAP in case of ContextType.Domain. If I try to use ContextType.Machine I have cannot use the workgroup/domain-name as PrincipalContext tries to connect directly to the machine. That fails if there is already a connection to that machine with that windows from the same machine.
So my question is:
Thank you for your replies.
Upvotes: 0
Views: 6079
Reputation: 21
Here is one that I just did for an app I'm working on myself - requires Framework v3.5 or greater....
public static bool Authenticate(string user, string password)
{
// Split the user name in case a domain name was specified as DOMAIN\USER
string[] NamesArray = user.Split(new char[] { '\\' }, 2);
// Default vars for names & principal context type
string DomainName = string.Empty;
string UserName = string.Empty;
ContextType TypeValue = ContextType.Domain;
// Domain name was supplied
if (NamesArray.Length > 1)
{
DomainName = NamesArray[0];
UserName = NamesArray[1];
}
else
{
// Pull domain name from environment
DomainName = Environment.UserDomainName;
UserName = user;
// Check this against the machine name to pick up on a workgroup
if (string.Compare(DomainName, System.Environment.MachineName, StringComparison.InvariantCultureIgnoreCase) == 0)
{
// Use the domain name as machine name (local user)
TypeValue = ContextType.Machine;
}
}
// Create the temp context
using (PrincipalContext ContextObject = new PrincipalContext(TypeValue, DomainName))
{
// Validate the credentials
return ContextObject.ValidateCredentials(UserName, password);
}
}
Upvotes: 2
Reputation: 1269
For the sake of completeness, here my solution which seems to do exactly what I want:
public class WinApiDomainAuthenticator
{
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
public static IPrincipal Authenticate(String domainUser, String password)
{
var userToken = IntPtr.Zero;
var creds = new DomainAuthCredentials(domainUser, password);
if (! LogonUser(creds.Username,
creds.Domain,
creds.Password,
(int)LogonType.LOGON32_LOGON_BATCH,
(int)LogonProvider.LOGON32_PROVIDER_DEFAULT, out userToken))
{
var error = new Win32Exception(Marshal.GetLastWin32Error());
throw new SecurityException("Error while authenticating user", error);
}
var identity = new WindowsIdentity(userToken);
if (userToken != IntPtr.Zero)
CloseHandle(userToken);
return ConvertWindowsIdentityToGenericPrincipal(identity);
}
protected static IPrincipal ConvertWindowsIdentityToGenericPrincipal(WindowsIdentity windowsIdentity)
{
if (windowsIdentity == null)
return null;
// Identity in format DOMAIN\Username
var identity = new GenericIdentity(windowsIdentity.Name);
var groupNames = new string[0];
if (windowsIdentity.Groups != null)
{
// Array of Group-Names in format DOMAIN\Group
groupNames = windowsIdentity.Groups
.Select(gId => gId.Translate(typeof(NTAccount)))
.Select(gNt => gNt.ToString())
.ToArray();
}
var genericPrincipal = new GenericPrincipal(identity, groupNames);
return genericPrincipal;
}
protected class DomainAuthCredentials
{
public DomainAuthCredentials(String domainUser, String password)
{
Username = domainUser;
Password = password;
Domain = ".";
if (!domainUser.Contains(@"\"))
return;
var tokens = domainUser.Split(new char[] { '\\' }, 2);
Domain = tokens[0];
Username = tokens[1];
}
public DomainAuthCredentials()
{
Domain = String.Empty;
}
#region Properties
public String Domain { get; set; }
public String Username { get; set; }
public String Password { get; set; }
#endregion
}
}
The LogonType and LogonProvider enums reflect the definitions in "Winbase.h". I settled with LogonType.LOGON32_LOGON_BATCH instead of LogonType.LOGON32_LOGON_NETWORK because samba 3.4.X seems to have trouble with this type.
Upvotes: 4