ADSI - Able to authenticate but unable to get memberOf information

D

deltalimagolf

I've got an asp.net site where it's setup to use forms authentication. I
take the username and password provided and authenticate them by calling the
LogonUser method (from advapi32). If that is successful I call the
DuplicateToken method and then using WindowsIdentity impersonate the user and
attempt to get the groups the user is a member of. Everything works up to
the attempt to get the groups, I get a 'The specified domain either does not
exist or could not be contacted.' error. The following code works on my
local box (even when running IE as a local - non AD - user), but when it is
deployed to a web server in the DMZ the error mentioned is seen.

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
string principal, string authority, string password,
LogonSessionType logonType, LogonProvider logonProvider, out IntPtr token);

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken, int
impersonationLevel, ref IntPtr hNewToken);

public static ArrayList UserMemberOf(string path, string username)
{
DirectorySearcher search = new DirectorySearcher(path);
search.Filter = "(sAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("memberOf");
ArrayList groups = new ArrayList();
try
{
SearchResult result = search.FindOne();
if (result != null)
{
int propertyCount = result.Properties["memberOf"].Count;
String dn;
int equalsIndex, commaIndex;

for (int propertyCounter = 0; propertyCounter < propertyCount;
propertyCounter++)
{
dn = (String)result.Properties["memberOf"][propertyCounter];

equalsIndex = dn.IndexOf("=", 1);
commaIndex = dn.IndexOf(",", 1);
if (-1 == equalsIndex)
{
return null;
}
groups.Add(dn.Substring((equalsIndex + 1), (commaIndex - equalsIndex)
- 1));
}
}
}
catch (Exception ex)
{
throw new Exception("Error obtaining group names ('" + path + "','" +
username + "'). " + ex.Message);
}
return groups;
}


protected void clkLogin(object sender, EventArgs e)
{
IntPtr iptrUserToken = IntPtr.Zero;
IntPtr iptrDuplicateToken = IntPtr.Zero;
try
{
bool bAuthenticated =
LDAP.LogonUser(txtUserName.Text,
GatewayAdministrationWebConfig.AuthenticationDomain, txtPassword.Text,
LDAP.LogonSessionType.NewCredentials, LDAP.LogonProvider.Default, out
iptrUserToken);
if (!bAuthenticated)
{
return;
}
if (LDAP.DuplicateToken(iptrUserToken,
(int)TokenImpersonationLevel.Impersonation, ref iptrDuplicateToken) != 0)
{
using (WindowsImpersonationContext winImpersonation =
WindowsIdentity.Impersonate(iptrDuplicateToken))
{
if (winImpersonation == null)
{
throw new Exception("impersonation context is null");
}
string sUserDn = "LDAP://[authentication server ip
address]/OU=[],dc=[],dc=net";
ArrayList alGroups = LDAP.UserMemberOf(sUserDn, txtUserName.Text);
winImpersonation.Undo();
if ((alGroups == null) || (alGroups.IndexOf("[AD Group]") == -1))
{
return;
}
FormsAuthenticationTicket authTicket =
new FormsAuthenticationTicket(1,
txtUserName.Text,
DateTime.Now,
DateTime.Now.AddMinutes(60),
false,
string.Empty);
string sTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie cookieTicket = new
HttpCookie(FormsAuthentication.FormsCookieName, sTicket);
Response.Cookies.Add(cookieTicket);
Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUserName.Text,
false));
}
}
else
{
return;
}
}
catch (Exception ex)
{
throw ex;
}
finally
{ // Free the tokens
if (iptrUserToken != IntPtr.Zero)
LDAP.CloseHandle(iptrUserToken);
if (iptrDuplicateToken != IntPtr.Zero)
LDAP.CloseHandle(iptrDuplicateToken);
}
}
 
J

Joe Kaplan

This seems like a waste. If you have a WindowsIdentity for the user, why
not just use the Groups property on the WindowsIdentity? The data you need
is already in memory at that point.

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
deltalimagolf said:
I've got an asp.net site where it's setup to use forms authentication. I
take the username and password provided and authenticate them by calling
the
LogonUser method (from advapi32). If that is successful I call the
DuplicateToken method and then using WindowsIdentity impersonate the user
and
attempt to get the groups the user is a member of. Everything works up to
the attempt to get the groups, I get a 'The specified domain either does
not
exist or could not be contacted.' error. The following code works on my
local box (even when running IE as a local - non AD - user), but when it
is
deployed to a web server in the DMZ the error mentioned is seen.

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
string principal, string authority, string password,
LogonSessionType logonType, LogonProvider logonProvider, out IntPtr
token);

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken, int
impersonationLevel, ref IntPtr hNewToken);

public static ArrayList UserMemberOf(string path, string username)
{
DirectorySearcher search = new DirectorySearcher(path);
search.Filter = "(sAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("memberOf");
ArrayList groups = new ArrayList();
try
{
SearchResult result = search.FindOne();
if (result != null)
{
int propertyCount = result.Properties["memberOf"].Count;
String dn;
int equalsIndex, commaIndex;

for (int propertyCounter = 0; propertyCounter < propertyCount;
propertyCounter++)
{
dn = (String)result.Properties["memberOf"][propertyCounter];

equalsIndex = dn.IndexOf("=", 1);
commaIndex = dn.IndexOf(",", 1);
if (-1 == equalsIndex)
{
return null;
}
groups.Add(dn.Substring((equalsIndex + 1), (commaIndex - equalsIndex)
- 1));
}
}
}
catch (Exception ex)
{
throw new Exception("Error obtaining group names ('" + path + "','" +
username + "'). " + ex.Message);
}
return groups;
}


protected void clkLogin(object sender, EventArgs e)
{
IntPtr iptrUserToken = IntPtr.Zero;
IntPtr iptrDuplicateToken = IntPtr.Zero;
try
{
bool bAuthenticated =
LDAP.LogonUser(txtUserName.Text,
GatewayAdministrationWebConfig.AuthenticationDomain, txtPassword.Text,
LDAP.LogonSessionType.NewCredentials, LDAP.LogonProvider.Default, out
iptrUserToken);
if (!bAuthenticated)
{
return;
}
if (LDAP.DuplicateToken(iptrUserToken,
(int)TokenImpersonationLevel.Impersonation, ref iptrDuplicateToken) != 0)
{
using (WindowsImpersonationContext winImpersonation =
WindowsIdentity.Impersonate(iptrDuplicateToken))
{
if (winImpersonation == null)
{
throw new Exception("impersonation context is null");
}
string sUserDn = "LDAP://[authentication server ip
address]/OU=[],dc=[],dc=net";
ArrayList alGroups = LDAP.UserMemberOf(sUserDn, txtUserName.Text);
winImpersonation.Undo();
if ((alGroups == null) || (alGroups.IndexOf("[AD Group]") == -1))
{
return;
}
FormsAuthenticationTicket authTicket =
new FormsAuthenticationTicket(1,
txtUserName.Text,
DateTime.Now,
DateTime.Now.AddMinutes(60),
false,
string.Empty);
string sTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie cookieTicket = new
HttpCookie(FormsAuthentication.FormsCookieName, sTicket);
Response.Cookies.Add(cookieTicket);
Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUserName.Text,
false));
}
}
else
{
return;
}
}
catch (Exception ex)
{
throw ex;
}
finally
{ // Free the tokens
if (iptrUserToken != IntPtr.Zero)
LDAP.CloseHandle(iptrUserToken);
if (iptrDuplicateToken != IntPtr.Zero)
LDAP.CloseHandle(iptrDuplicateToken);
}
}
 
D

deltalimagolf

Apparently I'm missing something, at what point to I have a WindowsIdentity
for the user? I'm using the WindowsIdentity.Impersonate method to get a
WindowsImpersonationContext, is that what you are referring to?
 
J

Joe Kaplan

Yes, exactly. You build the WindowsIdentity from the token and then just
access the Groups property. If you want friendly names instead of SIDs (you
probably do), then you just use the Translate method on the
IdentityReferenceCollection.

Do LDAP calls for this is much more complicated, slower and more error
prone.

We could definitely fix your LDAP code, but for this particular application
it isn't worth the effort.

Joe K.
 
D

deltalimagolf

Here's my new clkLogin method -

protected void clkLogin(object sender, EventArgs e)
{
IntPtr iptrUserToken = IntPtr.Zero;
IntPtr iptrDuplicateToken = IntPtr.Zero;
try
{
bool bAuthenticated =
LDAP.LogonUser(txtUserName.Text,
GatewayAdministrationWebConfig.AuthenticationDomain, txtPassword.Text,
LDAP.LogonSessionType.NewCredentials, LDAP.LogonProvider.Default, out
iptrUserToken);
if (!bAuthenticated)
{
[display error message]
}
else
{
if (LDAP.DuplicateToken(iptrUserToken,
(int)TokenImpersonationLevel.Impersonation, ref iptrDuplicateToken) != 0)
{
using (WindowsIdentity winIdentity = new WindowsIdentity(iptrUserToken))
{
IdentityReferenceCollection colRefIdentity =
winIdentity.Groups.Translate(typeof(NTAccount));
foreach (IdentityReference refIdentity in colRefIdentity)
{
if (refIdentity.Value.ToUpper().CompareTo([AD Group].ToUpper()) == 0)
{
FormsAuthenticationTicket authTicket =
new FormsAuthenticationTicket(1,
txtUserName.Text,
DateTime.Now,
DateTime.Now.AddMinutes(60),
false,
string.Empty);
string sTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie cookieTicket = new
HttpCookie(FormsAuthentication.FormsCookieName, sTicket);
Response.Cookies.Add(cookieTicket);
Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUserName.Text, false));
break;
}
}
}
}
}
}
catch (Exception ex)
{
throw ex;
}
finally
{ // Free the tokens
if (iptrUserToken != IntPtr.Zero)
LDAP.CloseHandle(iptrUserToken);
if (iptrDuplicateToken != IntPtr.Zero)
LDAP.CloseHandle(iptrDuplicateToken);
}
}

No matter what username and password I put in I'm getting the groups for the
user I'm logged into the box as (this is happening locally, I haven't
deployed it to the server in the dmz yet).

TIA,
Dave
 
J

Joe Kaplan

I don't understand why you are calling DuplicateToken. You don't need that,
do you? You also don't use the token for building the WindowsIdentity in
the code below, so that is a little strange.

I think the main problem is that you are using the NewCredentials logon type
here. That type of token only transitions to the user's token when you use
the network. For a local token, you should use Network logon type.

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
deltalimagolf said:
Here's my new clkLogin method -

protected void clkLogin(object sender, EventArgs e)
{
IntPtr iptrUserToken = IntPtr.Zero;
IntPtr iptrDuplicateToken = IntPtr.Zero;
try
{
bool bAuthenticated =
LDAP.LogonUser(txtUserName.Text,
GatewayAdministrationWebConfig.AuthenticationDomain, txtPassword.Text,
LDAP.LogonSessionType.NewCredentials, LDAP.LogonProvider.Default, out
iptrUserToken);
if (!bAuthenticated)
{
[display error message]
}
else
{
if (LDAP.DuplicateToken(iptrUserToken,
(int)TokenImpersonationLevel.Impersonation, ref iptrDuplicateToken) != 0)
{
using (WindowsIdentity winIdentity = new WindowsIdentity(iptrUserToken))
{
IdentityReferenceCollection colRefIdentity =
winIdentity.Groups.Translate(typeof(NTAccount));
foreach (IdentityReference refIdentity in colRefIdentity)
{
if (refIdentity.Value.ToUpper().CompareTo([AD Group].ToUpper()) == 0)
{
FormsAuthenticationTicket authTicket =
new FormsAuthenticationTicket(1,
txtUserName.Text,
DateTime.Now,
DateTime.Now.AddMinutes(60),
false,
string.Empty);
string sTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie cookieTicket = new
HttpCookie(FormsAuthentication.FormsCookieName, sTicket);
Response.Cookies.Add(cookieTicket);
Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUserName.Text,
false));
break;
}
}
}
}
}
}
catch (Exception ex)
{
throw ex;
}
finally
{ // Free the tokens
if (iptrUserToken != IntPtr.Zero)
LDAP.CloseHandle(iptrUserToken);
if (iptrDuplicateToken != IntPtr.Zero)
LDAP.CloseHandle(iptrDuplicateToken);
}
}

No matter what username and password I put in I'm getting the groups for
the
user I'm logged into the box as (this is happening locally, I haven't
deployed it to the server in the dmz yet).

TIA,
Dave

Joe Kaplan said:
Yes, exactly. You build the WindowsIdentity from the token and then just
access the Groups property. If you want friendly names instead of SIDs
(you
probably do), then you just use the Translate method on the
IdentityReferenceCollection.

[message snipped]

Joe K.
--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services
Programming"
http://www.directoryprogramming.net
 
D

deltalimagolf

The DuplicateToken was left over from the impersonation (I orginally got it
from an example I found). I changed the logon type to Network, it works
great on my local box but now the LogonUser method returns false (when
deployed to the server in the DMZ).

Current clkLogin method -

protected void clkLogin(object sender, EventArgs e)
{
IntPtr iptrUserToken = IntPtr.Zero;
try
{
bool bAuthenticated =
LDAP.LogonUser(txtUserName.Text,
GatewayAdministrationWebConfig.AuthenticationDomain, txtPassword.Text,
LDAP.LogonSessionType.Network, LDAP.LogonProvider.Default, out
iptrUserToken);
if (!bAuthenticated)
{
return;
}
else
{
using (WindowsIdentity winIdentity = new WindowsIdentity(iptrUserToken))
{
IdentityReferenceCollection colRefIdentity =
winIdentity.Groups.Translate(typeof(NTAccount));
foreach (IdentityReference refIdentity in colRefIdentity)
{
if (refIdentity.Value.ToUpper().CompareTo([AD Group]) == 0)
{
FormsAuthenticationTicket authTicket =
new FormsAuthenticationTicket(1,
txtUserName.Text,
DateTime.Now,
DateTime.Now.AddMinutes(60),
false,
string.Empty);
string sTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie cookieTicket = new
HttpCookie(FormsAuthentication.FormsCookieName, sTicket);
Response.Cookies.Add(cookieTicket);
Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUserName.Text, false));
break;
}
}
}
}
}
catch (Exception ex)
{
throw ex;
}
finally
{ // Free the tokens
if (iptrUserToken != IntPtr.Zero)
LDAP.CloseHandle(iptrUserToken);
}
}
 
J

Joe Kaplan

For the server in the DMZ, you need to make sure:
- It is domain joined to a domain that is part of the forest your users are
from or has a trust with that forest or domain somehow
- Users have rights to do network login. They usually do, but sometimes
people remove the "authenticated users" built in group from the "access this
computer from the network" local security policy setting and break this.

Note that if the machine is not domain joined to meet the first requirement,
you aren't going to be able to use LogonUser for this. You will probably be
better off using the ActiveDirectoryMembershipProvider and possibly
something like the open source AD role provider (www.codeplex.com/adrp) for
roles. It does the LDAP stuff for you.

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
deltalimagolf said:
The DuplicateToken was left over from the impersonation (I orginally got
it
from an example I found). I changed the logon type to Network, it works
great on my local box but now the LogonUser method returns false (when
deployed to the server in the DMZ).

Current clkLogin method -

protected void clkLogin(object sender, EventArgs e)
{
IntPtr iptrUserToken = IntPtr.Zero;
try
{
bool bAuthenticated =
LDAP.LogonUser(txtUserName.Text,
GatewayAdministrationWebConfig.AuthenticationDomain, txtPassword.Text,
LDAP.LogonSessionType.Network, LDAP.LogonProvider.Default, out
iptrUserToken);
if (!bAuthenticated)
{
return;
}
else
{
using (WindowsIdentity winIdentity = new WindowsIdentity(iptrUserToken))
{
IdentityReferenceCollection colRefIdentity =
winIdentity.Groups.Translate(typeof(NTAccount));
foreach (IdentityReference refIdentity in colRefIdentity)
{
if (refIdentity.Value.ToUpper().CompareTo([AD Group]) == 0)
{
FormsAuthenticationTicket authTicket =
new FormsAuthenticationTicket(1,
txtUserName.Text,
DateTime.Now,
DateTime.Now.AddMinutes(60),
false,
string.Empty);
string sTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie cookieTicket = new
HttpCookie(FormsAuthentication.FormsCookieName, sTicket);
Response.Cookies.Add(cookieTicket);
Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUserName.Text,
false));
break;
}
}
}
}
}
catch (Exception ex)
{
throw ex;
}
finally
{ // Free the tokens
if (iptrUserToken != IntPtr.Zero)
LDAP.CloseHandle(iptrUserToken);
}
}

Joe Kaplan said:
I don't understand why you are calling DuplicateToken. You don't need
that,
do you? You also don't use the token for building the WindowsIdentity in
the code below, so that is a little strange.

I think the main problem is that you are using the NewCredentials logon
type
here. That type of token only transitions to the user's token when you
use
the network. For a local token, you should use Network logon type.

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services
Programming"
http://www.directoryprogramming.net
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,710
Latest member
bernietqt

Latest Threads

Top