Monday, 9 May 2005

Password changing for free!

This “webpart” adds password change functionality to SharePoint, enabling remote users to control their password even if they can’t do a local logon.
USE AT YOUR OWN RISK

 

This has been tested in a small server farm and a single server farm, both in a AD (2003) environment, it should also work in any SharePoint farm, in a AD (2000 or 2003) environment.

It might work in a standalone SharePoint deployment (ie; no AD), but this is not tested…

 

It has been tested using both Integrated and Basic authentication to SharePoint (and the iisadmpwd virtual directory). Both authentication modes have been tested logging in from a remote source (non-domain member) using “DOMAIN\Username”, UPN (where multiple UPN suffixes have been configured in ADDT) and straight “username” using Basic authentication. Obviously, basic auth is not recommended for non-SSL implementations as

passwords will be sent in clear.

 

Installation and Configuration steps 1-9 will need to be repeated on each front-end server in a SharePoint farm with multiple (NLB or otherwise) frontend servers…

 

Appendices A & B are the source code change required to the asp pages

 

Installation and Configuration

1. Enable IIS Password changing (follow MSKB-555071)

        a. If the site is not running on HTTPS make sure you set the value to 1 when you run: “adsutil.vbs set w3svc/1/PasswordChangeFlags [value]

        b. Ensure the iisadmpwd virtual directory is running under the same AppPool as SharePoint

 

2. Add the Iisadmpwd virtual directory as an excluded managed path in SharePoint

 

3. In ADUC, delegate the “read user information” and “reset user password” permission to the account running the SharePoint AppPool

 

4. Test that password changing works by opening http://server_name/iisadmpwd/aexp2b.asp in your browser

        a. Resolve any problems found before continuing!

 

5. Browse to C:\WINDOWS\system32\inetsrv\iisadmpwd\ on the Front-end server

 

6. Edit “aexp2b.asp” in a text editor (after making a backup copy)

        a. Paste in the code found in Appendix A of this document

        b. Save the file

 

7. Edit “text.asp” in a text editor (after making a backup copy)

        a. Change the value of “L_BackTo_Text” to: "Return to Intranet home"

        b. Change the value of “L_PasswordToShort_Text” to reflect the password requirements of the domain (min length, complexity etc)

        c. Make any further display name changes required

        d. Save the file

 

8. Edit “achg.asp” in a text editor (after making a backup copy)

        a. Paste in the code found in Appendix B of this document

        b. Save the file

 

9. Change the file extension of all other asp pages in the directory to something other than “asp” (this increases the security of the password change system)

 

10. Add a “Page Viewer” webpart to the Area/Site of your choice and give the URL as “../../../../iisadmpwd/aexp2b.asp”

 

11. Change the title of the webpart as required

 

12. Give the webpart a fixed height of 250 pixels

 

13. Apply these changes

 

14. The webpart will display the password change boxes with the domain and username fields being RO, and the fonts/styles matching the rest of your

Area/Site

 

Appendix A

<%@Language="VBSCRIPT"%>
<HTML>
<!--#include file = "text.asp"-->
<title><%=L_Title_Text%></title>
<FONT COLOR=FFFFFF>
<STYLE>
</STYLE>
</FONT>
<head>
    <META HTTP-EQUIV="Content-Type" Content="text/html; charset=Windows-1252">
    <link rel="stylesheet" type="text/css" href="/_layouts/1033/styles/ows.css">
    <link rel="stylesheet" type="text/css" href="/_layouts/1033/styles/menu.css">
    <link rel="stylesheet" type="text/css" href="/_layouts/1033/styles/sps.css">
</head>
    <BODY BGCOLOR=#FFFFFF LINK=000000 VLINK=000000>
<%
On Error goto 0
    if Request.Form("cancel") <> "" then
        Response.Redirect(Request.QueryString)
    end if
 
dim domain, username, posbs, posat
    username = Request.Form("acct")
        if username <> "" then
            username = Server.HTMLEncode(username)
        else
            username = Server.HTMLEncode(Request.ServerVariables("REMOTE_USER"))
        end if
 
    domain = Request.Form("domain")
        if domain <> "" then
            domain = Server.HTMLEncode(domain)
        else
            posbs = Instr(1, username, "\")
posat = Instr(1, username, "@")
if posbs > 0 then
domain = Left(username, posbs - 1)
username = Right(username, len(username) - posbs)
elseif posat > 0 then
domain = Right(username, len(username) - posat)
username = Left(username, posat - 1)
else
set nw = Server.CreateObject("WScript.Network")
domain = nw.UserDomain
end if
end if
%>
<!-- Windows NT Server with IIS -->
<%if Instr(1,Request.ServerVariables("SERVER_SOFTWARE"), "IIS") > 0 then%>
<!--
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>
<TR VALIGN=CENTER>
<TD></TD>
<TD WIDTH=20> </TD>
<TD><FONT SIZE=+3 COLOR=#000000><B><%=L_ISM_Text%><BR> <FONT SIZE=-
1><%=L_IIS6_Text%><FONT></B></FONT></TD>
</TR>
</Table>
-->
<%end if%>
<!-- Windows NT Workstation with PWS -->
<%if Instr(1,Request.ServerVariables("SERVER_SOFTWARE"), "PWS") then%>
<!--
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>
<TR VALIGN=CENTER>
<TD></TD>
<TD WIDTH=20> </TD>
<TD><FONT SIZE=+3 COLOR=#000000><B><%=L_ISM_Text%><BR> <FONT SIZE=-
1><%=L_PWS_Text%><FONT></B></FONT></TD>
</TR>
</Table>
-->
<%end if%>
<p>
<form method="POST"
action="http://<%=Server.HTMLEncode(Request.ServerVariables("SERVER_NAME"))%>/iisadmpwd/achg.asp?<%=
Server.HTMLEncode(Request.QueryString)%>">
<table>
<tr>
<td>
<%=L_Domain_Text%></td><td>
<input type="text" name="domain" READONLY value="
<%
Response.Write domain
%>
"%
Response.Write domain
%>
"></td>
</tr>
<tr>
<td>
<%=L_Account_Text%></td><td>
<input type="text" name="acct" READONLY value="
<%
Response.Write username
%>
"%
Response.Write username
%>
"></td>
</tr>
<tr>
<td><%=L_OldPassword_Text%></td><td>
<input type="password" name="old" value=""></td>
</tr>
<tr>
<td><%=L_NewPassword_Text%></td><td><input type="password" name="new" value=""></td>
</tr>
<tr>
<td><%=L_Confirm_Text%></td><td><input type="password" name="new2" value=""></td>
</tr>
</table>
<p>
<input type="submit" value="<%=L_OK_Text%>">
<input type="submit" name="cancel" value="<%=L_Cancel_Text%>">
<input type="reset" value="<%=L_Reset_Text%>">
</form>
</body>
</html>





Appendix B





<%@Language="VBScript"%>
<HTML>
<!--#include file = "text.asp"-->
<title><%=L_Title_Text%></title>
<STYLE>
</STYLE>
<head>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=Windows-1252">
<link rel="stylesheet" type="text/css" href="/_layouts/1033/styles/ows.css">
<link rel="stylesheet" type="text/css" href="/_layouts/1033/styles/menu.css">
<link rel="stylesheet" type="text/css" href="/_layouts/1033/styles/sps.css">
</head>
<BODY BGCOLOR=#FFFFFF LINK=000000 VLINK=000000>
<%On Error goto 0%>
<%if Request.Form("cancel") <> "" then
if Request.Form("denyifcancel") <> "" then
Response.Status = "401 Unauthorized"
Response.End
else
Response.Redirect(Request.QueryString)
end if
Response.End
end if
%>
<!-- Windows NT Server with IIS -->
<%if Instr(1,Request.ServerVariables("SERVER_SOFTWARE"), "IIS") > 0 then%>
<!--
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>
<TR VALIGN=CENTER>
<TD></TD>
<TD WIDTH=20> </TD>
<TD><FONT SIZE=+3 COLOR=#000000><B><%=L_ISM_Text%><BR> <FONT SIZE=-
1><%=L_IIS6_Text%><FONT></B></FONT></TD>
</TR>
</Table>
-->
<%end if%>
<!-- Windows NT Workstation with PWS -->
<%if Instr(1,Request.ServerVariables("SERVER_SOFTWARE"), "PWS") then%>
<!--
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>
<TR VALIGN=CENTER>
<TD></TD>
<TD WIDTH=20> </TD>
<TD><FONT SIZE=+3 COLOR=#000000><B><%=L_ISM_Text%><BR> <FONT SIZE=-
1><%=L_PWS_Text%><FONT></B></FONT></TD>
</TR>
</Table>
-->
<%end if%>
<p>
<%if Request.Form("new") <> Request.Form("new2") then %>
<%=L_PWDM_Text%><p>
<%Response.Write "<br><H3><a href=" &
Server.HTMLEncode(Request.ServerVariables("HTTP_REFERER")) & ">" & L_Back_Text & " </a></H3>"
Response.End%>
<%end if%>
<%
On Error resume next
dim domain, posbs, posat, username, pUser, root
dim upn_name
upn_name = ""
domain = Trim(Request.Form("domain"))
' if no domain is present we try to get the domain from the username,
' e.g. domainusername or praesi@ultraschallpiloten.com
if domain = "" then
posbs = Instr(1,Request.Form("acct"),"\" )
posat = Instr(1,Request.Form("acct"),"@" )
if posbs > 0 then
domain = Left(Request.Form("acct"),posbs-1)
username = Right(Request.Form("acct"),len(Request.Form("acct")) - posbs)
elseif posat > 0 then
upn_name = Request.Form("acct")
domain = Right(upn_name, len(upn_name) - posat)
username = Left(upn_name, posat-1)
else
username = Request.Form("acct")
set nw = Server.CreateObject("WScript.Network")
domain = nw.Computername
end if
else
username = Trim(Request.Form("acct"))
end if
' verify that the characters in the user name are valid
if IsInvalidUsername(username) = true then
Response.Write L_InvalidUsername_Text & "."
Response.Write "<br><H3><a href=" &
Server.HTMLEncode(Request.ServerVariables("HTTP_REFERER")) & ">" & L_Back_Text & " </a></H3>"
Response.End
end if
' verify that the characters in the domain name are valid
if IsInvalidDomainname(domain) = true then
Response.Write L_InvalidDomainname_Text & "."
Response.Write "<br><H3><a href=" &
Server.HTMLEncode(Request.ServerVariables("HTTP_REFERER")) & ">" & L_Back_Text & " </a></H3>"
Response.End
end if
if upn_name = "" then
set pUser = GetObject("WinNT://" & domain & "/" & username & ",user")
if Not IsObject(pUser) then
set root = GetObject("WinNT:")
set pUser = root.OpenDSObject("WinNT://" & domain & "/" & username & ",user",
username, Request.Form("old"),1)
Response.Write "<!--OpenDSObject call-->"
end if
if Not IsObject(pUser) then
set pUser = Server.CreateObject("IIS.PwdChg")
pUser.Domain = domain
pUser.User = username
end if
else
set pUser = Server.CreateObject("IIS.PwdChg")
if Not IsObject(pUser) then
set pUser = GetObject("WinNT://" & domain & "/" & username & ",user")
if Not IsObject(pUser) then
set root = GetObject("WinNT:")
set pUser = root.OpenDSObject("WinNT://" & domain & "/" & username &
",user", username, Request.Form("old"),1)
Response.Write "<!--OpenDSObject call-->"
end if
else
pUser.Domain = domain
pUser.User = username
pUser.UPN = upn_name
end if
end if
if Not IsObject(pUser) then
'Response.Write "domain <> null - OpenDSObject also failed"
if err.number = -2147024843 then
Response.Write L_NotExist_Text & "."
else
if err.description <> "" then
Response.Write L_Error_Text & ": " & err.description
else
Response.Write L_Errornumber_Text & ": " & err.number
end if
Response.Write "<br><H3><a href=" &
Server.HTMLEncode(Request.ServerVariables("HTTP_REFERER")) & ">" & L_Back_Text & " </a></H3>"
end if
Response.End
end if
err.Clear
pUser.ChangePassword Request.Form("old"), Request.Form("new")
if err.number <> 0 then
if err.number = -2147024810 then
Response.Write "<p>" & L_Error_Text & ": " & L_Invalid_Text
elseif err.number = -2147022651 then
Response.Write L_PasswordToShort_Text
else
Response.Write L_Errornumber_Text & ": " & err.number
end if
Response.Write "<br><H3><a href=" &
Server.HTMLEncode(Request.ServerVariables("HTTP_REFERER")) & ">" & L_Back_Text & " </a></H3>"
Response.End
else
Response.Write L_PasswordChanged_Text & ".<p>"
end if
%>
<br>
<a href="http://<%=Server.HTMLEncode(Request.ServerVariables("SERVER_NAME"))%>/"
target="_top"><%=L_BackTo_Text%></a>
</body></html>
<%
function IsInvalidUsername(username)
dim re
set re = new RegExp
' list of invalid characters in a user name.
re.Pattern = "[/\\""\[\]:<>\+=;,@]"
IsInvalidUsername = re.Test(username)
end function
function IsInvalidDomainname(domainname)
dim re
set re = new RegExp
' list of invalid characters in a domain name.
re.Pattern = "[/\\""\[\]:<>\+=;,@!#$%^&\(\)\{\}\|~]"
IsInvalidDomainName = re.Test(domainname)
end function
%>