Saturday, December 25, 2010

SharePoint 2010: Linking SharePoint User to Active Directory User

While we are using SharePoint Foundation, Sometimes we need to get the active directory user details based on current logged in user. If you are using SharePoint Server then this is not a big deal as you can get the user details through user profile. However, if you are using SharePoint Foundation then there’s no shortcut way to getting user details. However, one of my client is using SharePoint Foundation and wanted to get the user details from active directory (say user’s First Name). Here’s how I’ve achieved this:

 

Step 1: Created a timer job to import user details from Active Directory

I have created a custom list to store imported data from Active Directory into SharePoint. The list looks like below

image

Figure 1: SharePoint List to keep Active Directory User Details

 

As shown in the figure 1, the SID is the key to map a SharePoint user to Active Directory User. The following code snippet shows the code to import data

First I created DTO class to represent LDAP User:

public class LdapUser
{
    public int ID { get; set; }
    public bool IsActive { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string SId { get; set; }
    public string DisplayName { get; set; }
    public string UserName { get; set; }
}

Figure 2: An DTO to keep Active directory and/or SharePoint list data

 

The following code is to get the Active Directory data into LdapUser DTO:

public IList<LdapUser> GetUsersFromActiveDirectory(string connectionString, string userName, string password)
{
    var users = new List<LdapUser>();
const int UF_ACCOUNTDISABLE = 0x0002; using (var directoryEntry = new DirectoryEntry(connectionString, userName, password, AuthenticationTypes.None)) { var directorySearcher = new DirectorySearcher(directoryEntry); directorySearcher.Filter = "(&(objectClass=user)(objectClass=person)(objectClass=organizationalPerson)(!objectClass=computer))"; var propertiesToLoad = new[] { "SAMAccountName", "displayName", "givenName", "sn", "mail", "userAccountControl", "objectSid" }; directorySearcher.PropertiesToLoad.AddRange(propertiesToLoad); foreach (SearchResult searchEntry in directorySearcher.FindAll()) { var userEntry = searchEntry.GetDirectoryEntry(); var ldapUser = new LdapUser(); ldapUser.DisplayName = NullHandler.GetString(userEntry.Properties["displayName"].Value); if (string.IsNullOrEmpty(ldapUser.DisplayName)) continue; ldapUser.Email = NullHandler.GetString(userEntry.Properties["mail"].Value); ldapUser.FirstName = NullHandler.GetString(userEntry.Properties["givenName"].Value); ldapUser.LastName = NullHandler.GetString(userEntry.Properties["sn"].Value); ldapUser.UserName = NullHandler.GetString(userEntry.Properties["SAMAccountName"].Value); var userAccountControl = (int)userEntry.Properties["userAccountControl"].Value; ldapUser.IsActive = (userAccountControl & UF_ACCOUNTDISABLE) != UF_ACCOUNTDISABLE; var sid = new SecurityIdentifier((byte[])userEntry.Properties["objectSid"][0], 0).Value; ldapUser.SId = sid; users.Add(ldapUser); } } return users; }

Figure 2: A method to get Active Directory User data in DTO format

 

I’ve used a helper class NullHandler above, which is shown below:

public class NullHandler
{
    public static string GetString(object value)
    {
        return (value == null || value == DBNull.Value) ? string.Empty : value.ToString();
    }
}

 

The above method GetUsersFromActiveDirectory return the Ldap dto from Active Directory. Then you need to save the Ldap dto into SharePoint. The following code shown the method that will save the Ldap dto in SharePoint list:

public void SaveActiveDirectoryUsersToSharePointList(SPWeb currentWeb, IList<LdapUser> ldapUsers)
{
    const string query = @"<Where><Eq><FieldRef Name='SID'/><Value Type='Text'>{0}</Value></Eq></Where>";
    var ldapUserList = currentWeb.Lists["LDAPUsers"];
    foreach (var ldapUser in ldapUsers)
    {
        SPQuery spQuery = new SPQuery();
        spQuery.Query =  string.Format(query, ldapUser.SId);
        var items = ldapUserList.GetItems(spQuery);

        SPListItem listItem;

        //if the user exists with the same Sid then update 
        //either create a new list item.
        if (items.Count == 1)
        {
            listItem = items[0];
        }
        else
        {
            listItem = ldapUserList.AddItem();
        }
        listItem[Constants.Lists.LdapUsersList.Email] = ldapUser.Email;
        listItem[Constants.Lists.LdapUsersList.FirstName] = ldapUser.FirstName;
        listItem[Constants.Lists.LdapUsersList.LastName] = ldapUser.LastName;
        listItem[Constants.Lists.LdapUsersList.DisplayName] = ldapUser.DisplayName;
        listItem[Constants.Lists.LdapUsersList.IsActive] = ldapUser.IsActive;
        listItem[Constants.Lists.LdapUsersList.PID] = ldapUser.SId;
        listItem[Constants.Lists.LdapUsersList.UserName] = ldapUser.UserName;
        listItem.Update();
    }
}

Figure 3: A method to save DTO (LdapUser) in SharePoint list.

 

In the above method SaveActiveDirectoryUsersToSharePointList, if a listitem with the same SId as in the LdapUsers list, then the list item is updated or a new one is added. So SId is key to synchronize list item and Active Directory item.

 

After User data is imported from Active Directory to SharePoint, the SharePoint list has use details. As shown in the image below, the user doceditor properties in Active Directory is shown on the left side whereas the imported SharePoint list in right side.

image

Figure 4: Active Directory User and SharePoint list item side-by-side

 

Finally you can create a timer job to sync data from Active Directory to SharePoint list. However, I’m skipping this step for brevity.

Step 2: Retrieve SPUser’s details from SharePoint List where Active Directory data imported

As shown in the code below, you can get current SharePoint user’s SId by accessing SPUser’s Sid property. Once you have the sid you can query the list (LDAPUsers) where you imported the user data from Active Directory.

var ldapList = currentWeb.Lists["LDAPUsers"];
var currentUserSid = currentWeb.CurrentUser.Sid;
var query = new SPQuery();
query.Query = string.Format(@"<Where>
                    <Eq>
                        <FieldRef Name='PID'  />
                        <Value Type='Text'>{0}</Value>
                    </Eq>
                </Where>", sid);
var items = ldapList.GetItems(query);
if (items.Count == 1)
{
    var ldapUserListItem = items[0];
}

Conclusion

So the Active directory sid is mapped to current SPUser’s Sid. So you can access the Active Directory user’s Sid using code shown below:

var sid = new SecurityIdentifier((byte[])userEntry.Properties["objectSid"][0], 0).Value;

Then you can get the SharePoint User’s Sid by using the code snippet below:

SPContext.Current.Web.CurrentUser.Sid

Once you have the mapping, you can import any data from Active Directory to SharePoint list, sync the data with timer job and get the data of current logged in user from SharePoint list.

31 comments:

  1. Thanks for the great article! I'm a bit confused about Foundation as far as being able to generate user lists for searching AD users. Can this be done using this same approach?

    ReplyDelete
  2. Yes you can fill a SharePoint list with AD users information with this approach.

    ReplyDelete
  3. thanks for sharing, VERY helpful!

    ReplyDelete
  4. Can you give me some more information? In Visual studio 2010, which template do I have to pick? web application? sharepoint 2010 sequential workflow/business data connectivity model?
    Or can you post this project?
    Thanks in advance!

    ReplyDelete
  5. Thanks for the post. I noticed you saved your information in the "LDAPUsers" list. Is it possible to save the info in the User Information List? I've tried and I seem to get an error, "Cannot complete this action"

    ReplyDelete
  6. I'm not sure but I think u can't directly access the userinformation list. Even if you can access the list you should not modify the list. Rather than directly adding the users, you can use SharePoint Object Model to add users to the site/web, which will eventually add the user in userinformation list.

    ReplyDelete
  7. Hi Sohel,

    Thanks for the article.It helped me much.


    Regards
    Pradeep

    ReplyDelete
  8. Has anybody heard/seen of a solution to update SharePoint Foundation User Profile information from Active Directory on a scheduled basis?

    ReplyDelete
  9. Hi Sohel,

    What does meant by NullHandler.GetString() method. Is NullHandler is a external class?
    What does meant by UF_ACCOUNTDISABLE?

    ReplyDelete
  10. @Vivek, NullHandler.GetString is a custom method, sorry I didn't put the code for this class in the post. Below is the code snippet:

    const int UF_ACCOUNTDISABLE = 0x0002;

    public class NullHandler
    {
    public static string GetString(object value)
    {
    return (value == null || value == DBNull.Value) ? string.Empty : value.ToString();
    }
    }

    ReplyDelete
  11. In SharePoint Foundation, you can add custom columns to the User Information List. How would I push active directory data into those columns for users that already exist in the user information list?

    ReplyDelete
  12. @Brendan, I suggest not to modify the user information list. Rather use another list to keep your custom information about user. And to sync data to the column, you can use timer job to read data from AD and push it to the list.

    ReplyDelete
  13. Where do I put the source code?
    I do not know.

    Thanks.

    ReplyDelete
  14. It depends on how you want to get the list populated. If you want to populate the list periodically, then u can run the code in timer job. If you want to do it by user interaction (say when user clicks a button) then you can put the code in web part button click events. Also you can put the code in console app or windows app that runs in the server.

    ReplyDelete
  15. Hello Sohel,
    I'm trying to use your method And I have following error:

    The name 'Constants' does not exist in the current context

    When I remove using System.Collections.Generic mistakes clears, But I need this .dll for Ilist, so I've got another mistake with Ilist.
    What am I doing wrong? Which Libraries do you include?

    ReplyDelete
  16. @Lena, The 'Constants.Lists.LdapUsersList.***' are the field names. Please replace this with your corresponding field name. I've shown the idea here. So the exact code copying will not work.

    ReplyDelete
  17. Do you mean this ?
    listItem["Email"] = ldapUser.Email;

    ))))) Thank you

    ReplyDelete
  18. Hello, how do I get the first step? First I created DTO class to represent LDAP User: where am I getting this from or doing this? I am not sure what this means?

    ReplyDelete
  19. @Matthew, you can put the method 'GetUsersFromActiveDirectory' in any exiting class or create a new class and put the method inside the class. The method 'SaveActiveDirectoryUsersToSharePointList' needs to be put inside timer job or console app or anything suitable for your design. I've not provided complete solution but provided code snippet so that people can put the code snippet in their existing project depending on their needs.

    ReplyDelete
  20. I apologize, I am not a coder. I don't know where to begin. I want to do EXACTLY what the end result is, I am just frustrated with how to "start" it. My need is how to start, am I going to do Visual Studio, etc. I go into SharePoint Designer from time to time, but don't go into Visual Studio as I am a "no code guy." I will slowly work on it if needed and with some assistance. Just wondering where I should start and also I have a guy who might be able to help me if I know where to being and or what might be the starting point and how to get to the end result if it is simple and easy with this example you have!
    Thank you again!

    ReplyDelete
  21. Hello, i am having the similar requirement, i.e i need to get the columns(FirstName & LastName) autofilled based on the UniqueID(PeoplePicker)column entered. i can do this with the help of SP Designer, but since firstname & lastname fields are not populated from AD to SP, i need to get firstname and lastname details from AD based on the UniqueID(entered from PeoplePicker)column in SharePoint 2010 list. Can you let me know how can i achieve this ? if possible share the code ?

    ReplyDelete
  22. Hello,

    I wonder how I can put everything in a single file with a. Ps1 and run in Cmdlet?

    If not how to proceed?

    thank you.

    ReplyDelete
  23. Being clearest possible!
    Where to put the codes to be run and what order?
    directly in Sharepoint Foundation?,
    In Sharepoint Designer?
    In Visual Studio 2010?

    thank you.

    ReplyDelete
  24. @Spacov, You need to create a new Visual Studio solution or use an existing one and you also need someone who is familiar with SharePoint Development. Then there's few ways you can use the code, timer job (if you want to run the synchronization periodically), web part (if you want to run the code on demand and has less users to sync.). I don't think you can use PowerShell to use the code above rather need .net code.

    ReplyDelete
  25. Hi Sohel,
    Excellent article.
    My requirement is similiar to yours. Except that I am wondering if it is to pull a group from AD or just a user's details to SharePoint.
    I need to pull a Group with all its Sub-Groups(or OUs) from AD into SharePoint. This group has 3 columns(Type, Name and Description), so if I pull a Group do I need to pull user's details explicitly or how should I proceed or make modifications to this code.
    Thank you!

    ReplyDelete
  26. Hi Sohel,
    Can you please explain Step 2: Retrieve SPUser’s details from SharePoint List where Active Directory data imported.
    Why do we need this after saving AD users to SharePoint List? I understood it is to query current user's SId but when and where will this code snippet be used?
    Also can you provide timer job code snippet(just to run the sync job periodically, I know how to write timerjob and activate the feature..)
    Thanks

    ReplyDelete
  27. Excellent information! Thanks for sharing this. It helped a great deal in setting up my solution.
    For anyone who might benefit from some additional code, I've shared my entire solution (including some additional features) here:
    http://community.spiceworks.com/scripts/show/2993-sharepoint-staff-directory-from-active-directory

    ReplyDelete