How to: Grouping groups

GroupPolicy

Today I would like to show a tip for grouping active directory groups using LINQ to objects.

Suppose this scenario: You wanna retrieve all the roles a user belongs, grouping them by their domain name, as shows:

Groups under:
– Group name: All
– Group name: LOCAL
Groups under: BUILTIN
– Group name: Users
– Group name: Administrators
Groups under: PRIMARY_DOMAIN_NAME
– Group name: xxx1
– Group name: xxx2
– Group name: xxx3
– Group name: xxx4
– Group name: xxxN
Groups under: NT AUTHORITY
– Group name: INTERACTIVE
– Group name: Authentified users

(*) As you can imagine, some names have been deleted for security reasons :-)

We can accomplish this using only one LINQ to objects sentence, and a couple of extension methods (extending the NTAccount class):

public static IOrderedEnumerable<IGrouping <string, NTAccount>>
    GetGroupsUnderDomains(this WindowsIdentity identity)
{
    var groups =
        from grIdentity in identity.Groups
        where grIdentity.IsValidTargetType(typeof(NTAccount))
        select grIdentity.Translate(typeof(NTAccount)) as NTAccount into ntAccounts
        let domainName = ntAccounts.GetDomainName()
        orderby domainName
        group ntAccounts by domainName
            into domainGroups
            orderby domainGroups.Key
            select domainGroups;
    return groups;
}

public static string GetDomainName(this NTAccount account)
{
    string[] split = account.Value.Split('\\');
    return split.Length == 1 ? string.Empty : split[0];
}

public static string GetAccountName(this NTAccount account)
{
    string[] split = account.Value.Split('\\');
    return split[split.Length - 1];
}

Awesome! LINQ rules :-)

If you would try this code, it’s quite simple:

var groups = WindowsIdentity.GetCurrent().GetGroupsUnderDomains();
foreach (var dg in groups)
{
    Console.WriteLine(string.Format("Groups under: {0}", dg.Key));
    foreach (var g in dg)
    {
        Console.WriteLine(string.Format("  - Group name: {0}", g.GetAccountName()));
    }
}

HYEI, happy coding!

December 2010

How to: Check if current user is member of ‘domain admins’

The scenario

Sometimes in business applications it’s interesting checking if current user is member of the ‘domain administrators’ role. For example I am used to checking if current user has administrative privileges in order to showing some advanced configuration options (changing the application’s connection string, or allow creating new users).

privileges_thumb

IsInRole

To acomplish this we could use WindowsPrincipal class and its IsInRole method (this method checks if an user is member of a Windows role and returns a bool value). One of its overrides allows to pass the SID of the role or a constant value based on the enumeration WindowsBuiltInrole.

Note: For performance reasons, it’s recommended to use the override: IsinRole(SecurityIdentifier).

To check if current user is a local administrator we only need to do this:

WindowsPrincipal wp = new WindowsPrincipal(WindowsIdentity.GetCurrent());
return wp.IsInRole(WindowsBuiltInRole.Administrator);

Notice that it’s realy easy, but as -its name indicates- WindowsBuiltInrole enumeration only contains local roles. So, if we would check if our user is member of a domain group, we should find the role SID, and then copy this value in our code.

As you can imagine, this is not the best solution, isn’t it? Well, let’s investigate a little bit more…

WellKnownSidType

Let’s take a look at the following enumeration WellKnownSidType, this enumeration provides commonly used security identifiers. Uhm… sounds good! Let’s try to use it in our code:

WindowsPrincipal wp = new WindowsPrincipal(WindowsIdentity.GetCurrent());
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.AccountDomainAdminsSid, null);
return wp.IsInRole(sid);

Do’h!It seems that we need to pass the second argument called DomainSId…

CurrentUserIsDomainAdminError

DomainSid

What the hell means DomainSId? At MSDN we can read a brief explanation: Represents the domain SID, and this value is required for some WellKnownSidType values.

At this point our goal should be know the domain SID, but wait… how can I retrieve the domain SID? After spend some time surfing the Internet, the only solution I could find was an utility called PSGetSid from Mark Russinovich inside PSTools components. If you want try it, you can download this utility and execute it from your console to know the SID of your domain (type your domain name as “microsoft.com” o “net.volvo.com”):

DomainSidConsole_thumb

However, I’m pretty sure exists a better solution to this issue. Thus, let’s try to ask to our domain their SID, using the namespace System.DirectoryServices:

Domain d = Domain.GetDomain(new DirectoryContext(DirectoryContextType.Domain, getDomainName()));
using (DirectoryEntry de = d.GetDirectoryEntry())
{
    byte[] domSid = (byte[])de.Properties["objectSid"].Value;
    string sdomainSid = sIDtoString(domSid);
    Console.WriteLine(sdomainSid);
}

Here we need a couple of auxiliar methods. The first one retrieves the domain name, and the second one retrieves a string that represents a SID value (from a byte array).

public static string getDomainName()
{
    return IPGlobalProperties.GetIPGlobalProperties().DomainName;
}

public static string sIDtoString(byte[] sidBinary)
{
    SecurityIdentifier sid = new SecurityIdentifier(sidBinary, 0);
    return sid.ToString();
}

Then, the value of sdomainSid corresponds to the SID of our domain. Great! Now, we can use it to retrieve the SID of the domain administrators role.

Putting it all together

Like my collegue and friend @alegrebandolero, I’m also a fan of extension methods. So, let’s create an extension method for the WindowsIdentity class:

using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Net.NetworkInformation;
using System.Security.Principal;

namespace TestAD
{
    public static class SecurityExtensions
    {
        public static bool IsDomainAdmin(this WindowsIdentity identity)
        {
            Domain d = Domain.GetDomain(new
                DirectoryContext(DirectoryContextType.Domain, getDomainName()));
            using (DirectoryEntry de = d.GetDirectoryEntry())
            {
                byte[] domainSIdArray = (byte[])de.Properties["objectSid"].Value;
                SecurityIdentifier domainSId = new SecurityIdentifier(domainSIdArray, 0);
                SecurityIdentifier domainAdminsSId = new SecurityIdentifier(
                WellKnownSidType.AccountDomainAdminsSid, domainSId);
                WindowsPrincipal wp = new WindowsPrincipal(identity);
                return wp.IsInRole(domainAdminsSId);
            }
        }

        private static string getDomainName()
        {
            return IPGlobalProperties.GetIPGlobalProperties().DomainName;
        }
    }
}

That’s all. Now, use it as follows:

if (WindowsIdentity.GetCurrent().IsDomainAdmin())
{
    //Some actions…
}

Edit 12/14/2010: Since Windows Vista, each Windows user have a couple of security tokens. The first one is the normal token with limited privileges, and the second one only works when you ‘run as administrator’. This code only works if you are using the second token, running the application as administrator.

HYEI, happy coding!

December 2010