Changing domain user password

H

Harry Devine

I've been searching around for an answer to this question, but haven't
gotten too far. I'm fairly new to ASP.NET, so I'm not sure how to setup
machine.config and web.config properly.

What I want to be able to do is allow a domain user to change their password
in the AD via a webpage. We have several users with domain accounts, but
they do not actually login to our domain as they are spread out all over the
country. I have a VBS script that notifies them when their password is due
to expire, starting 10 days out.

Since these users are not local to where my domain controller resides, they
have to call me or email me to have their password reset. I found an
example written by Ryan Dunn using an Impersonate function that he wrote
(www.dunnry.com), but I keep getting an error stating: "Parser Error
Message: The XML file
c:\winnt\microsoft.net\framework\v1.1.4322\Config\machine.config could not
be loaded. Either a required impersonation level was not provided, or the
provided impersonation level is invalid. "

This seems like, to me, a fundamental type of function to do, but info on
how to do it is all over the place. Does anyone have any good ideas or
steps on how to accomplish this?

Thanks for any help,
Harry
 
J

Joe Kaplan \(MVP - ADSI\)

The crux of doing this in a web page is simply to create a DirectoryEntry
object that is bound to the user's object in AD and invoking the
ChangePassword ADSI method. Impersonation may or may not be needed as you
need to prompt for the old password anyway, so it doesn't really hurt to
simply use those credentials in your DirectoryEntry constructor.

The error you are getting sounds like it is unrelated to an
DirectoryServices programming stuff though. Are you sure ASP.NET is working
in general?

Note also that Ryan and I have book coming out that covers this stuff in
detail, but it won't be available for a few more months now.

Posting an example of the code you are using would be a great start.

Joe K.
 
H

Harry Devine

I'm reasonably sure ASP.NET is working (the .aspx page that I'm putting
below comes up), but this is my first try at programming anything in it, so
take that for what it's worth. I'm including the code for the reset.aspx
file and web.config. The only change that I made to machine.config was to
change the username and password under the processModel section to be my
domain admin account (I found that suggestion somewhere on the Web while
researching the error).

Thanks for any help,
Harry

reset.aspx:
<%@ Assembly Name="System.DirectoryServices, Version=1.0.3300.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"%>
<%@ Assembly Name="Dunnry.Security" %>

<%@ Import Namespace="System.DirectoryServices" %>
<%@ Import Namespace="Dunnry.Security" %>

<HTML>
<script language="C#" runat="server">

void Page_Load(Object Src, EventArgs E ) {
if(!Page.IsPostBack)
{

}
}

private void ResetPassword(object sender, EventArgs e)
{
//for impersonation
string username = "AdminUser";
string password = "adminpwd";
string domain = "domain";

Impersonate i = new Impersonate(LogonProvider.LOGON32_PROVIDER_WINNT50);
i.ImpersonateUser(username, domain, password);

string ldapPath = LDAP://dc=mydomain,dc=com;
DirectoryEntry de = new DirectoryEntry(ldapPath);
de.AuthenticationType = AuthenticationTypes.Secure;
string qry =
String.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))",
txtUsername.Text);

DirectorySearcher ds = new DirectorySearcher(de,qry);
SearchResult sr = ds.FindOne();

if(sr==null)
{
lblMessage.Text = "User not found";
return;
}

try
{
DirectoryEntry user = sr.GetDirectoryEntry();
user.AuthenticationType = AuthenticationTypes.Secure;
user.Invoke("SetPassword", new object[]{txtPassword.Text});
lblMessage.Text = "Success <br>";
}
catch(Exception ex)
{
//throw ex;
lblMessage.Text = "Failure: " + ex.Message;
if(ex.InnerException != null)
lblMessage.Text += "<br>" + ex.InnerException.Message;
}
finally
{
de.Close();
i.UndoImpersonation();
}
}
</script>

<body>
<form runat="server">
UserName: <asp:textbox id="txtUsername" runat="server"/><br>
New Password: <asp:textbox id="txtPassword" runat="server"/><br>
<asp:button id="btnReset" runat="server" Text="Reset"
OnClick="ResetPassword" /><br>
<asp:label id="lblMessage" runat="server"/><br><br>
I am running as: <%=Context.User.Identity.Name %><br>
My process is running as:
<%=System.Security.Principal.WindowsIdentity.GetCurrent().Name %>
</form>
</body>
</HTML>


web.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true"/>
<authentication mode="Windows" />
<!--<authorization>
<deny users="?" />
</authorization>-->
</system.web>

<!-- Secure the .aspx page using web.config
<location path="reset.aspx">
<system.web>
<authorization>
<allow roles="DOMAIN\AdminUser" />
<deny users="*" />
</authorization>
</system.web>
</location> -->
</configuration>

processModel section of machine.config:
<processModel
enable="true"
timeout="Infinite"
idleTimeout="Infinite"
shutdownTimeout="0:00:05"
requestLimit="Infinite"
requestQueueLimit="5000"
restartQueueLimit="10"
memoryLimit="60"
webGarden="false"
cpuMask="0xffffffff"
userName="domain\adminuser"
password="adminpwd"
logLevel="Errors"
clientConnectedCheck="0:00:05"
comAuthenticationLevel="Connect"
comImpersonationLevel="Impersonate"
responseDeadlockInterval="00:03:00"
maxWorkerThreads="20"
maxIoThreads="20"
/>
 
J

Joe Kaplan \(MVP - ADSI\)

I thought you were doing password changes, not resets. There are actually
important differences on how that is approached.

It sounds like this is running on Win2K or XP, right? That's why you are
editing the processModel section.

It also looks like you are using the 1.0 version of the .NET Framework. Any
reason for that? 1.1 or 2.0 would be preferred. The 1.0.3300.0 version for
System.DirectoryServices points to the 1.0 framework.

If you have changed the process model account, there is no reason to also
impersonate the same account in code. The process account will be running
as the admin user. However, this is generally not considered a good
practice, especially if other apps may be running on the same server as they
will all run under this administrative account and have more privileges than
they should.

Can you also show the exact line where this is crashing? If they page
displays, it seems like there should be some indication of what the problem
was on the postback.

Joe K.

Harry Devine said:
I'm reasonably sure ASP.NET is working (the .aspx page that I'm putting
below comes up), but this is my first try at programming anything in it,
so take that for what it's worth. I'm including the code for the
reset.aspx file and web.config. The only change that I made to
machine.config was to change the username and password under the
processModel section to be my domain admin account (I found that
suggestion somewhere on the Web while researching the error).

Thanks for any help,
Harry

reset.aspx:
<%@ Assembly Name="System.DirectoryServices, Version=1.0.3300.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"%>
<%@ Assembly Name="Dunnry.Security" %>

<%@ Import Namespace="System.DirectoryServices" %>
<%@ Import Namespace="Dunnry.Security" %>

<HTML>
<script language="C#" runat="server">

void Page_Load(Object Src, EventArgs E ) {
if(!Page.IsPostBack)
{

}
}

private void ResetPassword(object sender, EventArgs e)
{
//for impersonation
string username = "AdminUser";
string password = "adminpwd";
string domain = "domain";

Impersonate i = new Impersonate(LogonProvider.LOGON32_PROVIDER_WINNT50);
i.ImpersonateUser(username, domain, password);

string ldapPath = LDAP://dc=mydomain,dc=com;
DirectoryEntry de = new DirectoryEntry(ldapPath);
de.AuthenticationType = AuthenticationTypes.Secure;
string qry =
String.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))",
txtUsername.Text);

DirectorySearcher ds = new DirectorySearcher(de,qry);
SearchResult sr = ds.FindOne();

if(sr==null)
{
lblMessage.Text = "User not found";
return;
}

try
{
DirectoryEntry user = sr.GetDirectoryEntry();
user.AuthenticationType = AuthenticationTypes.Secure;
user.Invoke("SetPassword", new object[]{txtPassword.Text});
lblMessage.Text = "Success <br>";
}
catch(Exception ex)
{
//throw ex;
lblMessage.Text = "Failure: " + ex.Message;
if(ex.InnerException != null)
lblMessage.Text += "<br>" + ex.InnerException.Message;
}
finally
{
de.Close();
i.UndoImpersonation();
}
}
</script>

<body>
<form runat="server">
UserName: <asp:textbox id="txtUsername" runat="server"/><br>
New Password: <asp:textbox id="txtPassword" runat="server"/><br>
<asp:button id="btnReset" runat="server" Text="Reset"
OnClick="ResetPassword" /><br>
<asp:label id="lblMessage" runat="server"/><br><br>
I am running as: <%=Context.User.Identity.Name %><br>
My process is running as:
<%=System.Security.Principal.WindowsIdentity.GetCurrent().Name %>
</form>
</body>
</HTML>


web.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true"/>
<authentication mode="Windows" />
<!--<authorization>
<deny users="?" />
</authorization>-->
</system.web>

<!-- Secure the .aspx page using web.config
<location path="reset.aspx">
<system.web>
<authorization>
<allow roles="DOMAIN\AdminUser" />
<deny users="*" />
</authorization>
</system.web>
</location> -->
</configuration>

processModel section of machine.config:
<processModel
enable="true"
timeout="Infinite"
idleTimeout="Infinite"
shutdownTimeout="0:00:05"
requestLimit="Infinite"
requestQueueLimit="5000"
restartQueueLimit="10"
memoryLimit="60"
webGarden="false"
cpuMask="0xffffffff"
userName="domain\adminuser"
password="adminpwd"
logLevel="Errors"
clientConnectedCheck="0:00:05"
comAuthenticationLevel="Connect"
comImpersonationLevel="Impersonate"
responseDeadlockInterval="00:03:00"
maxWorkerThreads="20"
maxIoThreads="20"
/>


Joe Kaplan (MVP - ADSI) said:
The crux of doing this in a web page is simply to create a DirectoryEntry
object that is bound to the user's object in AD and invoking the
ChangePassword ADSI method. Impersonation may or may not be needed as
you need to prompt for the old password anyway, so it doesn't really hurt
to simply use those credentials in your DirectoryEntry constructor.

The error you are getting sounds like it is unrelated to an
DirectoryServices programming stuff though. Are you sure ASP.NET is
working in general?

Note also that Ryan and I have book coming out that covers this stuff in
detail, but it won't be available for a few more months now.

Posting an example of the code you are using would be a great start.

Joe K.
 
H

Harry Devine

I wanted to allow a user to change their password to something else, so
maybe I've been confusing myself here. Our server is Win2K, and I was
changing the processModel section due to some info I found on the 'Net. I
can put it back to it's original form. Would you recommend upgrading to the
..NET framework 2.0 as well?

Basically, I'm looking for an example of allowing a user to hit the site,
enter their current password, and then enter a new one, using ASP.NET and
System.Directory services.

Sorry for the confusion, and thanks again for any help.
Harry

Joe Kaplan (MVP - ADSI) said:
I thought you were doing password changes, not resets. There are actually
important differences on how that is approached.

It sounds like this is running on Win2K or XP, right? That's why you are
editing the processModel section.

It also looks like you are using the 1.0 version of the .NET Framework.
Any reason for that? 1.1 or 2.0 would be preferred. The 1.0.3300.0
version for System.DirectoryServices points to the 1.0 framework.

If you have changed the process model account, there is no reason to also
impersonate the same account in code. The process account will be running
as the admin user. However, this is generally not considered a good
practice, especially if other apps may be running on the same server as
they will all run under this administrative account and have more
privileges than they should.

Can you also show the exact line where this is crashing? If they page
displays, it seems like there should be some indication of what the
problem was on the postback.

Joe K.

Harry Devine said:
I'm reasonably sure ASP.NET is working (the .aspx page that I'm putting
below comes up), but this is my first try at programming anything in it,
so take that for what it's worth. I'm including the code for the
reset.aspx file and web.config. The only change that I made to
machine.config was to change the username and password under the
processModel section to be my domain admin account (I found that
suggestion somewhere on the Web while researching the error).

Thanks for any help,
Harry

reset.aspx:
<%@ Assembly Name="System.DirectoryServices, Version=1.0.3300.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"%>
<%@ Assembly Name="Dunnry.Security" %>

<%@ Import Namespace="System.DirectoryServices" %>
<%@ Import Namespace="Dunnry.Security" %>

<HTML>
<script language="C#" runat="server">

void Page_Load(Object Src, EventArgs E ) {
if(!Page.IsPostBack)
{

}
}

private void ResetPassword(object sender, EventArgs e)
{
//for impersonation
string username = "AdminUser";
string password = "adminpwd";
string domain = "domain";

Impersonate i = new
Impersonate(LogonProvider.LOGON32_PROVIDER_WINNT50);
i.ImpersonateUser(username, domain, password);

string ldapPath = LDAP://dc=mydomain,dc=com;
DirectoryEntry de = new DirectoryEntry(ldapPath);
de.AuthenticationType = AuthenticationTypes.Secure;
string qry =
String.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))",
txtUsername.Text);

DirectorySearcher ds = new DirectorySearcher(de,qry);
SearchResult sr = ds.FindOne();

if(sr==null)
{
lblMessage.Text = "User not found";
return;
}

try
{
DirectoryEntry user = sr.GetDirectoryEntry();
user.AuthenticationType = AuthenticationTypes.Secure;
user.Invoke("SetPassword", new object[]{txtPassword.Text});
lblMessage.Text = "Success <br>";
}
catch(Exception ex)
{
//throw ex;
lblMessage.Text = "Failure: " + ex.Message;
if(ex.InnerException != null)
lblMessage.Text += "<br>" + ex.InnerException.Message;
}
finally
{
de.Close();
i.UndoImpersonation();
}
}
</script>

<body>
<form runat="server">
UserName: <asp:textbox id="txtUsername" runat="server"/><br>
New Password: <asp:textbox id="txtPassword" runat="server"/><br>
<asp:button id="btnReset" runat="server" Text="Reset"
OnClick="ResetPassword" /><br>
<asp:label id="lblMessage" runat="server"/><br><br>
I am running as: <%=Context.User.Identity.Name %><br>
My process is running as:
<%=System.Security.Principal.WindowsIdentity.GetCurrent().Name %>
</form>
</body>
</HTML>


web.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true"/>
<authentication mode="Windows" />
<!--<authorization>
<deny users="?" />
</authorization>-->
</system.web>

<!-- Secure the .aspx page using web.config
<location path="reset.aspx">
<system.web>
<authorization>
<allow roles="DOMAIN\AdminUser" />
<deny users="*" />
</authorization>
</system.web>
</location> -->
</configuration>

processModel section of machine.config:
<processModel
enable="true"
timeout="Infinite"
idleTimeout="Infinite"
shutdownTimeout="0:00:05"
requestLimit="Infinite"
requestQueueLimit="5000"
restartQueueLimit="10"
memoryLimit="60"
webGarden="false"
cpuMask="0xffffffff"
userName="domain\adminuser"
password="adminpwd"
logLevel="Errors"
clientConnectedCheck="0:00:05"
comAuthenticationLevel="Connect"
comImpersonationLevel="Impersonate"
responseDeadlockInterval="00:03:00"
maxWorkerThreads="20"
maxIoThreads="20"
/>


Joe Kaplan (MVP - ADSI) said:
The crux of doing this in a web page is simply to create a
DirectoryEntry object that is bound to the user's object in AD and
invoking the ChangePassword ADSI method. Impersonation may or may not
be needed as you need to prompt for the old password anyway, so it
doesn't really hurt to simply use those credentials in your
DirectoryEntry constructor.

The error you are getting sounds like it is unrelated to an
DirectoryServices programming stuff though. Are you sure ASP.NET is
working in general?

Note also that Ryan and I have book coming out that covers this stuff in
detail, but it won't be available for a few more months now.

Posting an example of the code you are using would be a great start.

Joe K.

I've been searching around for an answer to this question, but haven't
gotten too far. I'm fairly new to ASP.NET, so I'm not sure how to
setup machine.config and web.config properly.

What I want to be able to do is allow a domain user to change their
password in the AD via a webpage. We have several users with domain
accounts, but they do not actually login to our domain as they are
spread out all over the country. I have a VBS script that notifies
them when their password is due to expire, starting 10 days out.

Since these users are not local to where my domain controller resides,
they have to call me or email me to have their password reset. I found
an example written by Ryan Dunn using an Impersonate function that he
wrote (www.dunnry.com), but I keep getting an error stating: "Parser
Error Message: The XML file
c:\winnt\microsoft.net\framework\v1.1.4322\Config\machine.config could
not be loaded. Either a required impersonation level was not provided,
or the provided impersonation level is invalid. "

This seems like, to me, a fundamental type of function to do, but info
on how to do it is all over the place. Does anyone have any good ideas
or steps on how to accomplish this?

Thanks for any help,
Harry
 
J

Joe Kaplan \(MVP - ADSI\)

Ok. In this case, you really want to be using the ChangePassword method,
not SetPassword. SetPassword is for administrative resets. ChangePassword
is generally only performed by a user on their own account and requires the
current and new password.

ChangePassword can be a little tricky to get working on Win2K, especially if
your domain controller does not have an SSL certificate installed. They
don't by default, so someone would have needed to do this.

You will have trouble using Ryan's Impersonation class on Win2K server
though as an account must have the "act as part of the operating system"
privilege to call the LogonUser function he is using, and only SYSTEM has
that by default. You really should not be running your processModel as
SYSTEM. It is a very bad security practice! The problem you may be having
right now might relate to this.

As such, you probably want to consider doing regular impersonation combined
with Kerberos delegation. Kerberos delegation is an AD feature that allows
you to specify specific accounts that may log on to another service on the
network using the credentials the user originally supplied to them. In this
case, the web server would be logging in to AD on behalf of the user who
authenticated with the web site.

You would configure the app to use integrated auth in IIS (no anonymous),
configure ASP.NET for Windows auth and <identity impersonate="true"/> and
configure the machine account for the web server for Kerberos Delegation in
AD.

The code for doing the change would be similar, except that you would not do
programmatic impersonation and would invoke "ChangePassword", passing in
both the old and the new password.

You also want to be using SSL to protect this website since you are dealing
in plain text credentials. You don't want this traffic getting sniffed on
the wire!

Regarding the .NET Framework version, I definitely think you should be using
the 1.1 Framework minimum and possibly the 2.0 one. It is released now and
there is no good reason not to use it here. It has some nice bug fixes and
new features for System.DirectoryServices. The new features you don't need,
but the fixes are nice.

Your options get considerably better if you can upgrade your web server to
2003 SP1. Some of the limitations and difficulties go away and 2003 is
overall a much better web server platform in general.

HTH,

Joe K.

Harry Devine said:
I wanted to allow a user to change their password to something else, so
maybe I've been confusing myself here. Our server is Win2K, and I was
changing the processModel section due to some info I found on the 'Net. I
can put it back to it's original form. Would you recommend upgrading to
the .NET framework 2.0 as well?

Basically, I'm looking for an example of allowing a user to hit the site,
enter their current password, and then enter a new one, using ASP.NET and
System.Directory services.

Sorry for the confusion, and thanks again for any help.
Harry

Joe Kaplan (MVP - ADSI) said:
I thought you were doing password changes, not resets. There are actually
important differences on how that is approached.

It sounds like this is running on Win2K or XP, right? That's why you are
editing the processModel section.

It also looks like you are using the 1.0 version of the .NET Framework.
Any reason for that? 1.1 or 2.0 would be preferred. The 1.0.3300.0
version for System.DirectoryServices points to the 1.0 framework.

If you have changed the process model account, there is no reason to also
impersonate the same account in code. The process account will be
running as the admin user. However, this is generally not considered a
good practice, especially if other apps may be running on the same server
as they will all run under this administrative account and have more
privileges than they should.

Can you also show the exact line where this is crashing? If they page
displays, it seems like there should be some indication of what the
problem was on the postback.

Joe K.

Harry Devine said:
I'm reasonably sure ASP.NET is working (the .aspx page that I'm putting
below comes up), but this is my first try at programming anything in it,
so take that for what it's worth. I'm including the code for the
reset.aspx file and web.config. The only change that I made to
machine.config was to change the username and password under the
processModel section to be my domain admin account (I found that
suggestion somewhere on the Web while researching the error).

Thanks for any help,
Harry

reset.aspx:
<%@ Assembly Name="System.DirectoryServices, Version=1.0.3300.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"%>
<%@ Assembly Name="Dunnry.Security" %>

<%@ Import Namespace="System.DirectoryServices" %>
<%@ Import Namespace="Dunnry.Security" %>

<HTML>
<script language="C#" runat="server">

void Page_Load(Object Src, EventArgs E ) {
if(!Page.IsPostBack)
{

}
}

private void ResetPassword(object sender, EventArgs e)
{
//for impersonation
string username = "AdminUser";
string password = "adminpwd";
string domain = "domain";

Impersonate i = new
Impersonate(LogonProvider.LOGON32_PROVIDER_WINNT50);
i.ImpersonateUser(username, domain, password);

string ldapPath = LDAP://dc=mydomain,dc=com;
DirectoryEntry de = new DirectoryEntry(ldapPath);
de.AuthenticationType = AuthenticationTypes.Secure;
string qry =
String.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))",
txtUsername.Text);

DirectorySearcher ds = new DirectorySearcher(de,qry);
SearchResult sr = ds.FindOne();

if(sr==null)
{
lblMessage.Text = "User not found";
return;
}

try
{
DirectoryEntry user = sr.GetDirectoryEntry();
user.AuthenticationType = AuthenticationTypes.Secure;
user.Invoke("SetPassword", new object[]{txtPassword.Text});
lblMessage.Text = "Success <br>";
}
catch(Exception ex)
{
//throw ex;
lblMessage.Text = "Failure: " + ex.Message;
if(ex.InnerException != null)
lblMessage.Text += "<br>" + ex.InnerException.Message;
}
finally
{
de.Close();
i.UndoImpersonation();
}
}
</script>

<body>
<form runat="server">
UserName: <asp:textbox id="txtUsername" runat="server"/><br>
New Password: <asp:textbox id="txtPassword" runat="server"/><br>
<asp:button id="btnReset" runat="server" Text="Reset"
OnClick="ResetPassword" /><br>
<asp:label id="lblMessage" runat="server"/><br><br>
I am running as: <%=Context.User.Identity.Name %><br>
My process is running as:
<%=System.Security.Principal.WindowsIdentity.GetCurrent().Name %>
</form>
</body>
</HTML>


web.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true"/>
<authentication mode="Windows" />
<!--<authorization>
<deny users="?" />
</authorization>-->
</system.web>

<!-- Secure the .aspx page using web.config
<location path="reset.aspx">
<system.web>
<authorization>
<allow roles="DOMAIN\AdminUser" />
<deny users="*" />
</authorization>
</system.web>
</location> -->
</configuration>

processModel section of machine.config:
<processModel
enable="true"
timeout="Infinite"
idleTimeout="Infinite"
shutdownTimeout="0:00:05"
requestLimit="Infinite"
requestQueueLimit="5000"
restartQueueLimit="10"
memoryLimit="60"
webGarden="false"
cpuMask="0xffffffff"
userName="domain\adminuser"
password="adminpwd"
logLevel="Errors"
clientConnectedCheck="0:00:05"
comAuthenticationLevel="Connect"
comImpersonationLevel="Impersonate"
responseDeadlockInterval="00:03:00"
maxWorkerThreads="20"
maxIoThreads="20"
/>


"Joe Kaplan (MVP - ADSI)" <[email protected]>
wrote in message The crux of doing this in a web page is simply to create a
DirectoryEntry object that is bound to the user's object in AD and
invoking the ChangePassword ADSI method. Impersonation may or may not
be needed as you need to prompt for the old password anyway, so it
doesn't really hurt to simply use those credentials in your
DirectoryEntry constructor.

The error you are getting sounds like it is unrelated to an
DirectoryServices programming stuff though. Are you sure ASP.NET is
working in general?

Note also that Ryan and I have book coming out that covers this stuff
in detail, but it won't be available for a few more months now.

Posting an example of the code you are using would be a great start.

Joe K.

I've been searching around for an answer to this question, but haven't
gotten too far. I'm fairly new to ASP.NET, so I'm not sure how to
setup machine.config and web.config properly.

What I want to be able to do is allow a domain user to change their
password in the AD via a webpage. We have several users with domain
accounts, but they do not actually login to our domain as they are
spread out all over the country. I have a VBS script that notifies
them when their password is due to expire, starting 10 days out.

Since these users are not local to where my domain controller resides,
they have to call me or email me to have their password reset. I
found an example written by Ryan Dunn using an Impersonate function
that he wrote (www.dunnry.com), but I keep getting an error stating:
"Parser Error Message: The XML file
c:\winnt\microsoft.net\framework\v1.1.4322\Config\machine.config could
not be loaded. Either a required impersonation level was not provided,
or the provided impersonation level is invalid. "

This seems like, to me, a fundamental type of function to do, but info
on how to do it is all over the place. Does anyone have any good
ideas or steps on how to accomplish this?

Thanks for any help,
Harry
 
H

Harry Devine

We plan on upgrading our servers to Win2K3 early next year, so maybe I'll
put this off until then and get it working. I did upgrade the Framework to
2.0 in the mean time. How would I go about adding the machine account for
Kerberos Delegation?

Thanks,
Harry

Joe Kaplan (MVP - ADSI) said:
Ok. In this case, you really want to be using the ChangePassword method,
not SetPassword. SetPassword is for administrative resets.
ChangePassword is generally only performed by a user on their own account
and requires the current and new password.

ChangePassword can be a little tricky to get working on Win2K, especially
if your domain controller does not have an SSL certificate installed.
They don't by default, so someone would have needed to do this.

You will have trouble using Ryan's Impersonation class on Win2K server
though as an account must have the "act as part of the operating system"
privilege to call the LogonUser function he is using, and only SYSTEM has
that by default. You really should not be running your processModel as
SYSTEM. It is a very bad security practice! The problem you may be
having right now might relate to this.

As such, you probably want to consider doing regular impersonation
combined with Kerberos delegation. Kerberos delegation is an AD feature
that allows you to specify specific accounts that may log on to another
service on the network using the credentials the user originally supplied
to them. In this case, the web server would be logging in to AD on behalf
of the user who authenticated with the web site.

You would configure the app to use integrated auth in IIS (no anonymous),
configure ASP.NET for Windows auth and <identity impersonate="true"/> and
configure the machine account for the web server for Kerberos Delegation
in AD.

The code for doing the change would be similar, except that you would not
do programmatic impersonation and would invoke "ChangePassword", passing
in both the old and the new password.

You also want to be using SSL to protect this website since you are
dealing in plain text credentials. You don't want this traffic getting
sniffed on the wire!

Regarding the .NET Framework version, I definitely think you should be
using the 1.1 Framework minimum and possibly the 2.0 one. It is released
now and there is no good reason not to use it here. It has some nice bug
fixes and new features for System.DirectoryServices. The new features you
don't need, but the fixes are nice.

Your options get considerably better if you can upgrade your web server to
2003 SP1. Some of the limitations and difficulties go away and 2003 is
overall a much better web server platform in general.

HTH,

Joe K.

Harry Devine said:
I wanted to allow a user to change their password to something else, so
maybe I've been confusing myself here. Our server is Win2K, and I was
changing the processModel section due to some info I found on the 'Net. I
can put it back to it's original form. Would you recommend upgrading to
the .NET framework 2.0 as well?

Basically, I'm looking for an example of allowing a user to hit the site,
enter their current password, and then enter a new one, using ASP.NET and
System.Directory services.

Sorry for the confusion, and thanks again for any help.
Harry

Joe Kaplan (MVP - ADSI) said:
I thought you were doing password changes, not resets. There are
actually important differences on how that is approached.

It sounds like this is running on Win2K or XP, right? That's why you
are editing the processModel section.

It also looks like you are using the 1.0 version of the .NET Framework.
Any reason for that? 1.1 or 2.0 would be preferred. The 1.0.3300.0
version for System.DirectoryServices points to the 1.0 framework.

If you have changed the process model account, there is no reason to
also impersonate the same account in code. The process account will be
running as the admin user. However, this is generally not considered a
good practice, especially if other apps may be running on the same
server as they will all run under this administrative account and have
more privileges than they should.

Can you also show the exact line where this is crashing? If they page
displays, it seems like there should be some indication of what the
problem was on the postback.

Joe K.

I'm reasonably sure ASP.NET is working (the .aspx page that I'm putting
below comes up), but this is my first try at programming anything in
it, so take that for what it's worth. I'm including the code for the
reset.aspx file and web.config. The only change that I made to
machine.config was to change the username and password under the
processModel section to be my domain admin account (I found that
suggestion somewhere on the Web while researching the error).

Thanks for any help,
Harry

reset.aspx:
<%@ Assembly Name="System.DirectoryServices, Version=1.0.3300.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"%>
<%@ Assembly Name="Dunnry.Security" %>

<%@ Import Namespace="System.DirectoryServices" %>
<%@ Import Namespace="Dunnry.Security" %>

<HTML>
<script language="C#" runat="server">

void Page_Load(Object Src, EventArgs E ) {
if(!Page.IsPostBack)
{

}
}

private void ResetPassword(object sender, EventArgs e)
{
//for impersonation
string username = "AdminUser";
string password = "adminpwd";
string domain = "domain";

Impersonate i = new
Impersonate(LogonProvider.LOGON32_PROVIDER_WINNT50);
i.ImpersonateUser(username, domain, password);

string ldapPath = LDAP://dc=mydomain,dc=com;
DirectoryEntry de = new DirectoryEntry(ldapPath);
de.AuthenticationType = AuthenticationTypes.Secure;
string qry =
String.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))",
txtUsername.Text);

DirectorySearcher ds = new DirectorySearcher(de,qry);
SearchResult sr = ds.FindOne();

if(sr==null)
{
lblMessage.Text = "User not found";
return;
}

try
{
DirectoryEntry user = sr.GetDirectoryEntry();
user.AuthenticationType = AuthenticationTypes.Secure;
user.Invoke("SetPassword", new object[]{txtPassword.Text});
lblMessage.Text = "Success <br>";
}
catch(Exception ex)
{
//throw ex;
lblMessage.Text = "Failure: " + ex.Message;
if(ex.InnerException != null)
lblMessage.Text += "<br>" + ex.InnerException.Message;
}
finally
{
de.Close();
i.UndoImpersonation();
}
}
</script>

<body>
<form runat="server">
UserName: <asp:textbox id="txtUsername" runat="server"/><br>
New Password: <asp:textbox id="txtPassword" runat="server"/><br>
<asp:button id="btnReset" runat="server" Text="Reset"
OnClick="ResetPassword" /><br>
<asp:label id="lblMessage" runat="server"/><br><br>
I am running as: <%=Context.User.Identity.Name %><br>
My process is running as:
<%=System.Security.Principal.WindowsIdentity.GetCurrent().Name %>
</form>
</body>
</HTML>


web.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true"/>
<authentication mode="Windows" />
<!--<authorization>
<deny users="?" />
</authorization>-->
</system.web>

<!-- Secure the .aspx page using web.config
<location path="reset.aspx">
<system.web>
<authorization>
<allow roles="DOMAIN\AdminUser" />
<deny users="*" />
</authorization>
</system.web>
</location> -->
</configuration>

processModel section of machine.config:
<processModel
enable="true"
timeout="Infinite"
idleTimeout="Infinite"
shutdownTimeout="0:00:05"
requestLimit="Infinite"
requestQueueLimit="5000"
restartQueueLimit="10"
memoryLimit="60"
webGarden="false"
cpuMask="0xffffffff"
userName="domain\adminuser"
password="adminpwd"
logLevel="Errors"
clientConnectedCheck="0:00:05"
comAuthenticationLevel="Connect"
comImpersonationLevel="Impersonate"
responseDeadlockInterval="00:03:00"
maxWorkerThreads="20"
maxIoThreads="20"
/>


"Joe Kaplan (MVP - ADSI)" <[email protected]>
wrote in message The crux of doing this in a web page is simply to create a
DirectoryEntry object that is bound to the user's object in AD and
invoking the ChangePassword ADSI method. Impersonation may or may not
be needed as you need to prompt for the old password anyway, so it
doesn't really hurt to simply use those credentials in your
DirectoryEntry constructor.

The error you are getting sounds like it is unrelated to an
DirectoryServices programming stuff though. Are you sure ASP.NET is
working in general?

Note also that Ryan and I have book coming out that covers this stuff
in detail, but it won't be available for a few more months now.

Posting an example of the code you are using would be a great start.

Joe K.

I've been searching around for an answer to this question, but
haven't gotten too far. I'm fairly new to ASP.NET, so I'm not sure
how to setup machine.config and web.config properly.

What I want to be able to do is allow a domain user to change their
password in the AD via a webpage. We have several users with domain
accounts, but they do not actually login to our domain as they are
spread out all over the country. I have a VBS script that notifies
them when their password is due to expire, starting 10 days out.

Since these users are not local to where my domain controller
resides, they have to call me or email me to have their password
reset. I found an example written by Ryan Dunn using an Impersonate
function that he wrote (www.dunnry.com), but I keep getting an error
stating: "Parser Error Message: The XML file
c:\winnt\microsoft.net\framework\v1.1.4322\Config\machine.config
could not be loaded. Either a required impersonation level was not
provided, or the provided impersonation level is invalid. "

This seems like, to me, a fundamental type of function to do, but
info on how to do it is all over the place. Does anyone have any
good ideas or steps on how to accomplish this?

Thanks for any help,
Harry
 
J

Joe Kaplan \(MVP - ADSI\)

In AD users and computers, find the machine account and open its properties.
There should be Delegation tab where you can enable it. If your AD is 2003,
then you have options on which services the account can delegate to
(constrained delegation), but otherwise you just turn it on.

The only other thing to watch out for is that if you are using a DNS alias
to access the website which is different from the server's default DNS
registration, you will also need to set a service principal name for the
machine account as well that matches the additional DNS name. This is
required to ensure that your users are actually authenticating with the web
server with Kerberos (not NTLM, which does not support delegation).

If you put this off until your 2003 server upgrade, it might go more
smoothly. You could then simply use Ryan's impersonation class if you want.
All in all there are more options.

Best of luck,

Joe K.

Harry Devine said:
We plan on upgrading our servers to Win2K3 early next year, so maybe I'll
put this off until then and get it working. I did upgrade the Framework
to 2.0 in the mean time. How would I go about adding the machine account
for Kerberos Delegation?

Thanks,
Harry

Joe Kaplan (MVP - ADSI) said:
Ok. In this case, you really want to be using the ChangePassword method,
not SetPassword. SetPassword is for administrative resets.
ChangePassword is generally only performed by a user on their own account
and requires the current and new password.

ChangePassword can be a little tricky to get working on Win2K, especially
if your domain controller does not have an SSL certificate installed.
They don't by default, so someone would have needed to do this.

You will have trouble using Ryan's Impersonation class on Win2K server
though as an account must have the "act as part of the operating system"
privilege to call the LogonUser function he is using, and only SYSTEM has
that by default. You really should not be running your processModel as
SYSTEM. It is a very bad security practice! The problem you may be
having right now might relate to this.

As such, you probably want to consider doing regular impersonation
combined with Kerberos delegation. Kerberos delegation is an AD feature
that allows you to specify specific accounts that may log on to another
service on the network using the credentials the user originally supplied
to them. In this case, the web server would be logging in to AD on
behalf of the user who authenticated with the web site.

You would configure the app to use integrated auth in IIS (no anonymous),
configure ASP.NET for Windows auth and <identity impersonate="true"/> and
configure the machine account for the web server for Kerberos Delegation
in AD.

The code for doing the change would be similar, except that you would not
do programmatic impersonation and would invoke "ChangePassword", passing
in both the old and the new password.

You also want to be using SSL to protect this website since you are
dealing in plain text credentials. You don't want this traffic getting
sniffed on the wire!

Regarding the .NET Framework version, I definitely think you should be
using the 1.1 Framework minimum and possibly the 2.0 one. It is released
now and there is no good reason not to use it here. It has some nice bug
fixes and new features for System.DirectoryServices. The new features
you don't need, but the fixes are nice.

Your options get considerably better if you can upgrade your web server
to 2003 SP1. Some of the limitations and difficulties go away and 2003
is overall a much better web server platform in general.

HTH,

Joe K.

Harry Devine said:
I wanted to allow a user to change their password to something else, so
maybe I've been confusing myself here. Our server is Win2K, and I was
changing the processModel section due to some info I found on the 'Net.
I can put it back to it's original form. Would you recommend upgrading
to the .NET framework 2.0 as well?

Basically, I'm looking for an example of allowing a user to hit the
site, enter their current password, and then enter a new one, using
ASP.NET and System.Directory services.

Sorry for the confusion, and thanks again for any help.
Harry

"Joe Kaplan (MVP - ADSI)" <[email protected]>
wrote in message I thought you were doing password changes, not resets. There are
actually important differences on how that is approached.

It sounds like this is running on Win2K or XP, right? That's why you
are editing the processModel section.

It also looks like you are using the 1.0 version of the .NET Framework.
Any reason for that? 1.1 or 2.0 would be preferred. The 1.0.3300.0
version for System.DirectoryServices points to the 1.0 framework.

If you have changed the process model account, there is no reason to
also impersonate the same account in code. The process account will be
running as the admin user. However, this is generally not considered a
good practice, especially if other apps may be running on the same
server as they will all run under this administrative account and have
more privileges than they should.

Can you also show the exact line where this is crashing? If they page
displays, it seems like there should be some indication of what the
problem was on the postback.

Joe K.

I'm reasonably sure ASP.NET is working (the .aspx page that I'm
putting below comes up), but this is my first try at programming
anything in it, so take that for what it's worth. I'm including the
code for the reset.aspx file and web.config. The only change that I
made to machine.config was to change the username and password under
the processModel section to be my domain admin account (I found that
suggestion somewhere on the Web while researching the error).

Thanks for any help,
Harry

reset.aspx:
<%@ Assembly Name="System.DirectoryServices, Version=1.0.3300.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"%>
<%@ Assembly Name="Dunnry.Security" %>

<%@ Import Namespace="System.DirectoryServices" %>
<%@ Import Namespace="Dunnry.Security" %>

<HTML>
<script language="C#" runat="server">

void Page_Load(Object Src, EventArgs E ) {
if(!Page.IsPostBack)
{

}
}

private void ResetPassword(object sender, EventArgs e)
{
//for impersonation
string username = "AdminUser";
string password = "adminpwd";
string domain = "domain";

Impersonate i = new
Impersonate(LogonProvider.LOGON32_PROVIDER_WINNT50);
i.ImpersonateUser(username, domain, password);

string ldapPath = LDAP://dc=mydomain,dc=com;
DirectoryEntry de = new DirectoryEntry(ldapPath);
de.AuthenticationType = AuthenticationTypes.Secure;
string qry =
String.Format("(&(objectClass=user)(objectCategory=person)(sAMAccountName={0}))",
txtUsername.Text);

DirectorySearcher ds = new DirectorySearcher(de,qry);
SearchResult sr = ds.FindOne();

if(sr==null)
{
lblMessage.Text = "User not found";
return;
}

try
{
DirectoryEntry user = sr.GetDirectoryEntry();
user.AuthenticationType = AuthenticationTypes.Secure;
user.Invoke("SetPassword", new object[]{txtPassword.Text});
lblMessage.Text = "Success <br>";
}
catch(Exception ex)
{
//throw ex;
lblMessage.Text = "Failure: " + ex.Message;
if(ex.InnerException != null)
lblMessage.Text += "<br>" + ex.InnerException.Message;
}
finally
{
de.Close();
i.UndoImpersonation();
}
}
</script>

<body>
<form runat="server">
UserName: <asp:textbox id="txtUsername" runat="server"/><br>
New Password: <asp:textbox id="txtPassword" runat="server"/><br>
<asp:button id="btnReset" runat="server" Text="Reset"
OnClick="ResetPassword" /><br>
<asp:label id="lblMessage" runat="server"/><br><br>
I am running as: <%=Context.User.Identity.Name %><br>
My process is running as:
<%=System.Security.Principal.WindowsIdentity.GetCurrent().Name %>
</form>
</body>
</HTML>


web.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true"/>
<authentication mode="Windows" />
<!--<authorization>
<deny users="?" />
</authorization>-->
</system.web>

<!-- Secure the .aspx page using web.config
<location path="reset.aspx">
<system.web>
<authorization>
<allow roles="DOMAIN\AdminUser" />
<deny users="*" />
</authorization>
</system.web>
</location> -->
</configuration>

processModel section of machine.config:
<processModel
enable="true"
timeout="Infinite"
idleTimeout="Infinite"
shutdownTimeout="0:00:05"
requestLimit="Infinite"
requestQueueLimit="5000"
restartQueueLimit="10"
memoryLimit="60"
webGarden="false"
cpuMask="0xffffffff"
userName="domain\adminuser"
password="adminpwd"
logLevel="Errors"
clientConnectedCheck="0:00:05"
comAuthenticationLevel="Connect"
comImpersonationLevel="Impersonate"
responseDeadlockInterval="00:03:00"
maxWorkerThreads="20"
maxIoThreads="20"
/>


"Joe Kaplan (MVP - ADSI)" <[email protected]>
wrote in message The crux of doing this in a web page is simply to create a
DirectoryEntry object that is bound to the user's object in AD and
invoking the ChangePassword ADSI method. Impersonation may or may
not be needed as you need to prompt for the old password anyway, so
it doesn't really hurt to simply use those credentials in your
DirectoryEntry constructor.

The error you are getting sounds like it is unrelated to an
DirectoryServices programming stuff though. Are you sure ASP.NET is
working in general?

Note also that Ryan and I have book coming out that covers this stuff
in detail, but it won't be available for a few more months now.

Posting an example of the code you are using would be a great start.

Joe K.

I've been searching around for an answer to this question, but
haven't gotten too far. I'm fairly new to ASP.NET, so I'm not sure
how to setup machine.config and web.config properly.

What I want to be able to do is allow a domain user to change their
password in the AD via a webpage. We have several users with domain
accounts, but they do not actually login to our domain as they are
spread out all over the country. I have a VBS script that notifies
them when their password is due to expire, starting 10 days out.

Since these users are not local to where my domain controller
resides, they have to call me or email me to have their password
reset. I found an example written by Ryan Dunn using an Impersonate
function that he wrote (www.dunnry.com), but I keep getting an error
stating: "Parser Error Message: The XML file
c:\winnt\microsoft.net\framework\v1.1.4322\Config\machine.config
could not be loaded. Either a required impersonation level was not
provided, or the provided impersonation level is invalid. "

This seems like, to me, a fundamental type of function to do, but
info on how to do it is all over the place. Does anyone have any
good ideas or steps on how to accomplish this?

Thanks for any help,
Harry
 

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,769
Messages
2,569,582
Members
45,071
Latest member
MetabolicSolutionsKeto

Latest Threads

Top