Friday, April 8, 2011

Change ADS Password Webpart

I got an interesting request to build a SharePoint webpart that allow user to change their AD password. Mean, the webpart need to authenticate and update user password against LDAP.

Supposed if your environment have Office Outlook Web Access (OWA), the same feature can be found here.
  • In OWA.
  • Click "Options" button on the top right corner
  • The click the "Change Password" in left navigation.
  • The change password screen should look like this.

There are third party vendors out there who sell the similar webpart as well...

Anyway, to fulfill the request, I have built a SP 2010 webpart which will provide the same functionality, and because it is a webpart, you can deploy the feature to your site collection and place it on any page. The visual of the webpart will look like image below.


This sample webpart will
  • Show user login name
  • Password last update
  • Allow user to change existing password
A copy of this project can be download from here (Coming Soon)… 

If you are keen of building one from scratch... please follow the instruction below. Before you start, you need to obtain the LDAP information from your network administrator.

Change ADS Password Webpart

1) Launch Visual Studio 2010
2) On File menu, select New Project
3) Under Visual C#, SharePoint, 2010, Select Visual Web Part
4) Project Name: ChangeADSPassword
5) Click “OK” to create the project files

6) In Solution Explorer
7) Delete “VisualWebPart1” (created by default)
8) Add a New Item
9) Under Visual C#, SharePoint, 2010, select “Visual Web Part”
10) Named ChangePasswordWebPart, click OK
11) You may be prompt to specific your SharePoint URL
  • Once the project is created, these are the files (in green and red) you need to make changes


12) Right click Reference and Add “System.DirectoryServices

13) Double click “ChangePasswordWebPartUserControl.ascx
14) Paste the following code at the end of the page

<style type="text/css">
    .requiredField
    {
        color: #ff0000;
    }
    .xxSmallfont
    {
        font-size: xx-small;
    }
</style>
<br />
<table border="0">
    <tr>
        <td>You are logged on as:</td>
        <td><asp:Label ID="lblUser" runat="server" Text="" style="font-weight: 700"></asp:Label></td>
    </tr>
    <tr>
        <td>Password last updated on:</td>
        <td><asp:Label ID="lblPwdLastSet" runat="server" Text="" style="font-weight: 700"></asp:Label></td>
    </tr>
    <tr>
        <td></td>
        <td></td>
    </tr>
    <tr>
        <td>Current Password:<span class="requiredField">*</span></td>
        <td><asp:TextBox ID="oldpassword" runat="server" TextMode="Password"></asp:TextBox></td>
    </tr>
    <tr>
        <td>New Password:<span class="requiredField">*</span></td>
        <td><asp:TextBox ID="newpassword" runat="server" TextMode="Password"></asp:TextBox></td>
    </tr>
    <tr>
        <td>Confirm New Password:<span class="requiredField">*</span></td>
        <td><asp:TextBox ID="checknewpassword" runat="server" TextMode="Password"></asp:TextBox></td>
    </tr>
    <tr>
        <td>
        </td>
        <td>
            <asp:Button ID="btnChangePassword" runat="server" Text="Change Password"
                onclick="btnChangePassword_Click" /></td>
    </tr>
    <tr>
        <td colspan=2><asp:Label ID="lblOutput" runat="server" Text=""></asp:Label>
        </td>
    </tr>
</table>

15) Right Click and select “View Code” or open ChangePasswordWebPartUserControl.ascx.cs
16) Add the following code at the using section

using System.DirectoryServices;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;

17) Highlight the
        protected void Page_Load(object sender, EventArgs e)
        {
        }

18) Replace with 

        private string ldapPath;

        public string LdapPath
        {
            get { return ldapPath; }
            set { ldapPath = value; }
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            try
            {
                if (!Page.IsPostBack)
                {
                    string strLoginName = GetCurrentUser();
                    lblUser.Text = strLoginName;
                    lblPwdLastSet.Text += SearchUserByProperties(strLoginName, "pwdLastSet");
                }
            }
            catch (Exception ex)
            {
                lblOutput.Text = ex.Message.ToString();
            }
        }

        protected void btnChangePassword_Click(object sender, EventArgs e)
        {
            if ((oldpassword.Text.Length > 0) || (newpassword.Text.Length > 0) || (checknewpassword.Text.Length > 0))
            {
                if (newpassword.Text.ToString() == checknewpassword.Text.ToString())
                {
                    string strLoginName = GetCurrentUser();
                    lblOutput.Text = ChangeAcctPassword(strLoginName, oldpassword.Text, checknewpassword.Text);
                }

                else
                {
                    lblOutput.Text = "Passwords don't match";
                }
            }
            else
            {
                lblOutput.Text = "You must provide all require field";
            }
        }

        /// <summary>
        /// Update Account Password
        /// </summary>
        /// <param name="strLoginName"></param>
        /// <param name="oldPassword"></param>
        /// <param name="newPassword"></param>
        /// <returns></returns>
        private string ChangeAcctPassword(string strLoginName, string oldPassword, string newPassword)
        {
            string strOutput = string.Empty;
            string domainPath = ldapPath;

            try
            {
                DirectoryEntry domain = new DirectoryEntry(domainPath, strLoginName, oldPassword, AuthenticationTypes.Secure);

                DirectorySearcher search = new DirectorySearcher(domain);
                search.Filter = string.Format("(sAMAccountName={0})", strLoginName);
                search.SearchScope = SearchScope.Subtree;
                search.CacheResults = false;

                SearchResultCollection results = search.FindAll();
                if (results != null)
                {
                    try
                    {
                        foreach (SearchResult result in results)
                        {
                            domain = result.GetDirectoryEntry();
                        }

                        domain.Invoke("ChangePassword", new object[] { oldPassword, newPassword });
                        domain.CommitChanges();
                        strOutput += "<BR>Password is changed";
                    }
                    catch (Exception ex)
                    {
                        strOutput += string.Format("<BR>{0}<BR>Password couldn't be changed due to Password Policy restrictions", ex.Message.ToString());
                    }
                }
                else
                {
                    strOutput += "<BR>User not found or bad password";
                }
            }
            catch (Exception er)
            {
                strOutput += er.Message.ToString();
            }
            return strOutput;
        }

        /// <summary>
        /// Search AD User by properties
        /// </summary>
        /// <param name="LoginName"></param>
        /// <param name="strProperty"></param>
        /// <returns></returns>
        public string SearchUserByProperties(string LoginName, string strProperty)
        {
            string strReturn = string.Empty;
            string domainPath = ldapPath;

            using (DirectoryEntry domain = new DirectoryEntry(domainPath))
            {
                try
                {
                    DirectorySearcher search = new DirectorySearcher(domain);
                    search.Filter = string.Format("(&(objectclass=user)(objectcategory=person)(sAMAccountName={0}))", LoginName);
                    search.SearchScope = SearchScope.Subtree;
                    search.PropertiesToLoad.AddRange(new string[] { "cn", "sn", strProperty });

                    SearchResultCollection results = search.FindAll();
                    if (results != null)
                    {
                        foreach (SearchResult result in results)
                        {
                            if (result != null)
                            {
                                TimeSpan pwdLastSet = TimeSpan.MinValue;

                                if (result.Properties.Contains(strProperty))
                                {
                                    pwdLastSet = TimeSpan.FromTicks((long)result.Properties[strProperty][0]);

                                    long fileTime = (long)result.Properties[strProperty][0];
                                    DateTime pwdSet = DateTime.FromFileTime(fileTime);

                                    //strReturn += result.Properties["cn"][0];
                                    strReturn = pwdSet.ToString();
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    strReturn = ex.Message;
                }
            }
            return strReturn;
        }

        /// <summary>
        /// Strip off domain and return current user login name
        /// </summary>
        /// <returns></returns>
        private string GetCurrentUser()
        {
            SPWeb webContext = SPControl.GetContextWeb(Context);
            string strLoginName = webContext.CurrentUser.LoginName;

            int iPosition = strLoginName.IndexOf("\\") + 1;
            strLoginName = strLoginName.Substring(iPosition);
            return strLoginName;
        }

19) Then double click on ChangePasswordWebpart.cs
20) Highlight the

        protected override void CreateChildControls()
        {
            Control control = Page.LoadControl(_ascxPath);
            Controls.Add(control);
        }

21) Replace with

        // Visual Studio might automatically update this path when you change the Visual Web Part project item.
        private const string _ascxPath = @"~/_CONTROLTEMPLATES/ChangeADSPassword/ChangePasswordWebPart/ChangePasswordWebPartUserControl.ascx";

        //default LDAP (check with administrator for actual dns)
        private string strDefaultLDAPPath = "LDAP://DC01.SP2010.com";

        private string ldapDomain;
        [Category("Custom Properties"),
        Personalizable(PersonalizationScope.Shared),
        WebBrowsable, WebDisplayName("Domain Path:(LDAP://domain.com)"),
        WebDescription("LDAP Domain Path")]
        public string LdapDomain
        {
            get { return ldapDomain; }
            set { ldapDomain = value; }
        }

        protected override void CreateChildControls()
        {
            Control control = Page.LoadControl(_ascxPath);
            ChangePasswordWebPartUserControl ctl = control as ChangePasswordWebPartUserControl;
            ctl.LdapPath = ((ldapDomain != null && ldapDomain.Length > 0)) ? ldapDomain : strDefaultLDAPPath;
            Controls.Add(ctl);
            this.Title = "Change Password";
        }


22) Save All files then click “Build” to check for error


23) If successful, then click “deploy” to test your webpart
24) If you follow the instruction correctly, you should find ChangeADSPassword.wsp locate at the BIN folder (debug/release) of your project file location, which you can deploy manually.

25) Go to your development site
26) Add a webpart, under custom (should look like this).

27) After added onto the page, the visual should look like this.

28) Edit the webpart, under the Custom Properties section, is where you specify the LDAP information.

29) Stop Editing and start testing.

Done!

As said, OWA has the same built in password page... but if OWA is not available in your project for whatever reason, I hope you find this webpart useful! Good luck!

Also checkout
Codeplex for Change Password web part:
http://changepassword.codeplex.com/
Or, if you want the complete SharePoint AD TOOL
http://adselfservice.codeplex.com/

Disclaimer:
The information and code sample provide as it. I hope it will help you get going… The webpart should work in typical ADS and SP2010 environment; if not, please get further support from MSDN library, search engine or check out 3rd party webparts.
 
Thanks!

5 comments:

  1. I get an error
    Exception has been thrown by the target of an invocation.
    Password couldn't be changed due to Password Policy restrictions

    What should I check?
    Very goo work

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Hi. I found the solution. You are testing it with wrong domain settings. Turn off the setting, which restrict you to change passwords for users more than once per day.

      Delete
  2. Hi,

    Thanks for a such a nice article. It helped me a lot. I was able to follow your instructions and able to create the webpart and also deployed sucessfully.
    The moment i added to the any page Logged user showing correctly. The error shown for the "Password last updated on" next to that showing "The server is not operational". What could be the reason? how to overcome this error.Please help me. Thanks in advance.

    ReplyDelete
  3. Hi,

    Thanks for this great tutorial.

    I got this error:

    "The server is not operational" next to "Password last updated on"

    Thanks in advanced.

    ReplyDelete