LsaLookupSids

One of my coworkers got married and changed her surname. After my colleague changed her domain user strange thing happened. We are using domain authentication on our web portal. After domain user change she didn’t have any rights on the portal anymore. I checked the log (I am using Elmah) and there it was: Validation failed for one or more entities. The strangest thing was that under User column there was old surname, not the new one! Firstly I checked AD. Everything was fine. Then I tried to restart web page and IIS – it didn’t help. Server reboot was out of option.

After that I asked uncle Google, if he knows anything about that stuff. And he directed me to stackoverflow article. To work around this issue you can do following:

  1. Open registry editor as admin (on Windows Server 2008 and newer you can find it via searchbox (type regedit) on older versions you can run it via Run).
  2. Locate following subkey: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa
  3. Right-click on it, point to New and click DWORD Value.
  4. Type in LsaLookupCacheMaxSize and press Enter.
  5. Set value to 0 and exit registry editor.

Warning: this registry entry disables local SID caching. Because local SID cache helps to reduce domain controller workload and network traffic I deleted this registry entry after I checked that cache doesn’t hold deprecate username value anymore.

You can also check solution on official Microsoft support page.

How to override IIS default error messages?

Today I was working on retrieving status codes in ASP.NET MVC 4 application. I needed correct status codes for my ajax requests to show correct error message (in case of an error, of course). The problem was that IIS was redirecting when status code was added to response. In case of status code 401 it redirected to basic login, in case of status code 403 it redirected to IIS generic error page and so on. If you are working with .NET version 4.5 there exists very simple solution:

Response.TrySkipIisCustomErrors = true;

But not with .NET version 4.0. 🙂 In that case you have to add few lines into your Web.config file inside of system.webServer node:

<httpErrors existingResponse="PassThrough">
  <remove statusCode="403"/>
  <error statusCode="403" responseMode="ExecuteURL" path="~/StatusCode/Unauthorized"/>
  <remove statusCode="401"/>
  <error statusCode="401" responseMode="ExecuteURL" path="~/StatusCode/Forbidden"/>
  <remove statusCode="404"/>
  <error statusCode="404" responseMode="ExecuteURL" path="~/StatusCode/NotFound"/>
</httpErrors>

With attribute value “PassThrough” (attribute existingResponse) you tell IIS to leave response untouched if an existing response exists. You can check other attribute options here. Value of attribute path inside of node error represents the path to the response (for example: “~/ControllerName/ActionName”). That way you can override IIS’ default error responses with your custom error responses.

How to get user names and surnames and show them in ASP.NET MVC 4 autocomplete?

Today I was working on some autocompletion with users (name + surname) from Active Directory (from now on AD) in ASP.NET MVC 4 application. I wanted to get suggestions on the fly based on user’s input. I was accessing AD using Lightweight Directory Access Protocol (from now on LDAP).

You have to add two references into your project: System.DirectoryServices.dll and System.DirectoryServices.AccountManagement.dll. In your class you have to use two namespaces:

using System.DirectoryServices.AccountManagement;
using System.DirectoryServices;
In your controller you do something like this:
public JsonResult AutocompleteUsers(string term)
{
    string filter = "(&(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))(|(givenName={0}*)(sn={1}*)))";
    List persons = new List();

    DirectoryEntry dE = new DirectoryEntry(); //initialization
    {
        //uniquely identifies entry in a networked environment
        dE.Path = "directory entry path"; 

        //username for client authentication
        dE.Username = "directory entry username"; 
        
        //password for client authentication
        dE.Password = "directory entry password";

        //authentication types
        dE.AuthenticationType = AuthenticationTypes.ServerBind; 
    }
    
    DirectorySearcher dS = new DirectorySearcher(dE); //applying search filter
    {
        dS.Filter = String.Format(filter, term, term);
    }

    foreach (SearchResult result in dS.FindAll())
    {
        if (result != null)
        {
            DirectoryEntry entry = result.GetDirectoryEntry();
            
            string name = (entry.Properties["givenName"].Value ?? "").ToString();
            string surname = (entry.Properties["sn"].Value ?? "").ToString();

            if (!String.IsNullOrEmpty(name) && !String.IsNullOrEmpty(surname)) 
            {
                persons.Add(name + " " + surname);
            }
        }
    }

    IEnumerable ipersons = persons.AsEnumerable().OrderBy(p => p);

    return this.Json(ipersons, JsonRequestBehavior.AllowGet);
}
So, firstly you initialize connection with AD. After that you apply search filter. Part (objectCategory=person) returns user and contact objects. Because we only want users, we also need part (!userAccountControl:1.2.840.113556.1.4.803:=2). Contacts don’t have userAccountControl attribute, so this part will always be True for them. With part (objectClass=user) we restrict query to just user object. Part (|(givenName={0}*)(sn={1}*))) restricts query on users which name or surname starts with specified string. After that we loop through search results and get the names.
Collection of names is returned as Json. We must allow get with option JsonRequestBehavior.AllowGet. By default “Json get” is not allowed and without mentioned option exception is produced (Internal Server Error).On client site we have to attach following JavaScript code:

$(document).ready(function () {
    $('#textbox_id').autocomplete({
	source: '@Url.Action("action_name", "controller_name")'
    });
});
Textbox value (inserted string) is automaticaly sent in query string as parameter term. We must not forget to import jQuery scripts:
@Styles.Render("~/Content/themes/base/css")
@Scripts.Render("~/bundles/jquery")    
@Scripts.Render("~/bundles/jqueryui")

Without that callback won’t happen. 🙂

How to redirect non-www to www and domain to subdomain?

I recently wanted to permanently redirect all non-www urls to www urls and after that temporarily redirect everything to some subdomain. I found out that all that can be achieved with only few lines in .htaccess file:

# turn rewrite engine on
RewriteEngine On

# permanently (R=301) redirected example.com to www.example.com
RewriteCond %{HTTP_HOST} ^example.com$ [NC]
RewriteRule ^(.*) http://www.example.com/$1 [R=301,L]

# temporarily (R=302) redirected www.example.com to subdomain.example.com
RewriteCond %{HTTP_HOST} ^www.example.com$ [NC]
RewriteRule ^(.*) http://subdomain.example.com/$1 [R=302,L]

 

As simple as that. 🙂