Musings by Generator

Development, Life and everything else in S.A.

Change Active Directory Password Web Part for hosted solutions

A while ago I wrote a little web part for my company to use in our hosted environments that allows the logged in user to change their password. I won’t put any of the design up here, just the functional code in the “Change Password” event that connects to AD and changes the password since the IP for this web part is owned by my employer.

So I have to make the assumption if you are reading this, you know how to code a web part in Visual Studio 2010, or at least have access to a developer who can. This is also not the cleanest code I have written, but it works and is stable:

The basics

There are a couple of objects from .Net that I used, but they all reside in the namespace System.DirectoryServices, the assembly has the same name. To make the actual change you have to use a type of reflection from the class System.DirectoryServices.DirectoryEntry. I will explain what happens, but first here is the code:

SPSecurity.RunWithElevatedPrivileges(() =>
{
    SPWeb webContext = SPControl.GetContextWeb(Context);

    string userLogin = webContext.CurrentUser.LoginName;

    int position = userLogin.IndexOf("\\") + 1;
    userLogin = userLogin.Substring(position);

    DirectoryEntry entry = new DirectoryEntry(String.Format("LDAP://{0}", _domain), userLogin, TextBoxCurrentPassword.Text, AuthenticationTypes.Secure);
    DirectorySearcher search = new DirectorySearcher(entry);
    search.Filter = String.Format("(SAMAccountName={0})", userLogin);
    search.SearchScope = SearchScope.Subtree;
    search.CacheResults = false;

    SearchResultCollection results = search.FindAll();

    if (results.Count > 0)
    {
        foreach (SearchResult result in results)
        {
            try
            {
                entry = result.GetDirectoryEntry();
            }
            catch (Exception searchError)
            {
                LabelResults.Text += searchError.Message + "<br/>";
            }
        }

        try
        {
            entry.Invoke("ChangePassword", new object[] { TextBoxCurrentPassword.Text, TextBoxNewPassword.Text });
            entry.CommitChanges();
            LabelResults.ForeColor = Color.Green;
            LabelResults.Text = "Your password has been changed!";
        }
        catch (Exception changeError)
        {
            if (changeError.InnerException != null)
                LabelResults.Text = changeError.InnerException.Message;
            else
                LabelResults.Text = changeError.Message;
        }
    }
    else
        LabelResults.Text = "User not found";
});
 

 

So what is happening here. The actual AD code is from DirectoryEntry down. This code is setup so that when the web part is used on a page, the administrator can point it to a particular server in the web part settings.

So we first grab the current logged in user details, and use DirectorySearcher to find the user account. The domain to search through is specified in the DirectoryEntry instance. A filter is also applied to ensure that the it only searches for the logged in user.  We also tell the search to only look at the sub-tree of the specified LDAP.

If we found results, we grab the entry, an assumption is made that the search will ever only find one result since we search for a username which is unique in active directory.

Now that we have the active directory entry for the user, we need to invoke the the “ChangePassword” function. It takes the user’s current password and the new password that they want to change it to. Finally, we tell the entry to commit the changes.

Extra Note:

On the web part, the standard route is taken so the user is required to input their current password and the new password twice. So there are checks before the above code to ensure that the two versions of the new password match as well. All the other checks are present in the code above.

Disclaimer:

Please read the disclaimer if you plan on using anything from this article.

Best code fix and comments

I was reading through an old question on Stackoverflow about the best comments found in code, ironically, developers tend to submit the worst comments for these types of questions. I think we like the irony of the comments, or submit them on the basis of hard they make us laugh.

One particular submission had me in hysterics by the time i had finished it, it was submitted by someone who worked with the author, Dan, of the class below, they thoughtfully changed the name of the person who ticked Dan off to Richard. So I have included the class in it’s entirety and then I have a link to Dan’s blog post where he explains the code a bit and why he felt the need to use the wording that he did. Happy reading all.

//Code sanitized to protect the foolish.
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Web.UI;

namespace Mobile.Web.Control
{
    /// <summary>
    /// Class used to work around Richard being a fucking idiot
    /// </summary>
    /// <remarks>
    /// The point of this is to work around his poor design so that paging will
    /// work on a mobile control. The main problem is the BindCompany() method,
    /// which he hoped would be able to do everything. I hope he dies.
    /// </remarks>
    public abstract class RichardIsAFuckingIdiotControl : MobileBaseControl, ICompanyProfileControl
    {
        protected abstract Pager Pager { get; }

        public void BindCompany(int companyId) { }

        public RichardIsAFuckingIdiotControl()
        {
            MakeSureNobodyAccidentallyGetsBittenByRichardsStupidity();
        }

        private void MakeSureNobodyAccidentallyGetsBittenByRichardsStupidity()
        {
            // Make sure nobody is actually using that fucking bindcompany method
            MethodInfo m = this.GetType().GetMethod("BindCompany", BindingFlags.DeclaredOnly |
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            if (m != null)
            {
                throw new RichardIsAFuckingIdiotException("No!! Don't use the fucking BindCompany method!!!");
            }
            // P.S. this method is a joke ... the rest of the class is fucking serious
        }

        /// <summary>
        /// This returns true if this control is supposed to be doing anything
        /// at all for this request. Richard thought it was a good idea to load
        /// the entire website during every request and have things turn themselves
        /// off. He also thought bandanas and aviator sunglasses were "fuckin'
        /// gnarly, dude."
        /// </summary>
        protected bool IsThisTheRightPageImNotSureBecauseRichardIsDumb()
        {
            return Request.QueryString["Section"] == this.MenuItemKey;
        }

        protected override void OnLoad(EventArgs e)
        {
            if (IsThisTheRightPageImNotSureBecauseRichardIsDumb())
            {
                Page.LoadComplete += new EventHandler(Page_LoadComplete);
                Pager.RowCount = GetRowCountBecauseRichardIsDumb();
            }
            base.OnLoad(e);
        }

        protected abstract int GetRowCountBecauseRichardIsDumb();
        protected abstract void BindDataBecauseRichardIsDumb();

        void Page_LoadComplete(object sender, EventArgs e)
        {
            BindDataBecauseRichardIsDumb();
        }

        // the rest of his reduh-ndant interface members
        public abstract string MenuItemName { get; set; }
        public abstract string MenuItemKey { get; set; }
        public abstract bool IsCapable(CapabilityCheck checker, int companyId);
        public abstract bool ShowInMenu { get; }
        public virtual Control CreateHeaderControl()
        {
            return null;
        }
    }
}

Here is Dan’s article, titled From the Annals of Dubious Achievement.

Older Posts »