Using ADAM Principals in Authorization Manager

 

My name is Sudheer Mamidipaka. I am working in Windows Security Access Control team. I own testing of AzMan component.

 

We have lots of customers asking, if it’s possible to use AzMan to authorize ADAM principles. YES YOU CAN. But it just takes a little custom code. Here are some details and some sample code and scripts to do this:

 

Like Active Directory principals, ADAM principals can be assigned to groups (ADAM groups), and have credentials (username and password.) Unlike Active Directory principals, ADAM principals cannot logon to a Windows desktops or fileshares, or be authenticated through Windows Integrated Authentication. This means that applications that use ADAM principals need to authenticate the user credentials and query group memberships manually using LDAP interfaces. Typically applications authenticate ADAM principals using the ldap_bind API or a higher level wrapper such as Active Directory Services Interfaces (ADSI.) and query a users groups by querying the user’s tokenGroups attribute.

 

This is done by the addition of interfaces which allow applications to create an Authorization Manager empty context and then add the SIDs (user and group) to that context and then the ability to set the distinguished name of the ADAM principal for use by Authorization Manager dynamic ldap query groups. A custom management user interface is utilized to add the ADAM user and group SIDs to the role assignment.

There are two methods of authorizing ADAM principals with Authorization Manager.

The first approach checks for a SID match using the access check.  It is faster as it requires no searches. This approach is preferred in most development efforts due to speed.  It involves the following:

The applications that have authenticated an ADAM principal query the ADAM principal’s user and group SIDs in ADAM (done by using LDAP to query the principal’s tokenGroups attribute). The application adds the Sids to an Authorization Manager client context via the IAzClientContext2::AddSids interface. The application then provides the client context with the principal’s distinguished name (DN) by using the LDAPQueryDN attribute on the AzClientContext2 object.

The steps are as follows:

To create an ADAM group and assign a user to it:

Create ADAM Group (using ADAM ADSI Edit, LDP.EXE, another tool, or code)

Add ADAM principal to ADAM Group

In a custom Authorization Manager UI, create a Role Assignment and assign the ADAM user or group to a role (a custom UI is needed because the Windows Object Picker does not currently support ADAM.)

For testing purposes you could use the LDP.exe tool to retrieve the ADAM user or group sid and the Authorization Manager scriptable interfaces to assign the user or group to a role (such as the IAzRole::AddMember method.)

 

The application that uses Authorization Manager for application performs the following steps (see code sample below for detail):

Initialize the store and application (explained in previous section.)

  1. After a client connects and has been authenticated (typically via ldap_bind) create a client context using the IAzClientContext::InitializeClientContext2 interface
  2. Query the user’s objectSid attribute to obtain the user’s SID add this to the empty client context via the IAzCleintContext::AddStringSids method.

 

  1. Query the users tokenGroups attribute which will contain the user group SIDs (see sample code below.) Add ADAM group Sids to the client context object created above via the IAzCleintConetxt2::AddSids method.
  2. Query the client’s distinguished name in ADAM (see sample code below.)
  3. Add principal DN to client context via IAzClientContext2::LdapQueryDN which will support dynamic LDAP query groups

 

Here is the sample code for the above scenario. The code will

  • Authenticate ADAM user
  • Queries ADAM for ADAM user’s SID and groups SIDs
  • Initializes AzMan store and Application
  • Creates and empty ClientContext
  • Adds user and groups SIDs to the client context.
  • Sets User DN on the client context for Ldap queries
  • Calls an AccessCheck.

using System;

using System.Collections;

using System.Collections.Generic;

using System.DirectoryServices;

using System.Runtime.InteropServices;

using System.Text;

using Microsoft.Interop.Security.AzRoles;

namespace AzManADAMAuth

{

    class Program

    {

        static void Main(string[] args)

        {

            AuthenticationTypes AuthType = AuthenticationTypes.None;

            string UserDN, UserSid;

            ArrayList TokenGroupSids;

  if (args.GetLength(0) < 7)

            {

                Console.WriteLine("usage:\n \"AdamLogin\" \"ServerName\" \"Partition\" \"UserDN\" \"UserPassword\" \"AzManStoreURL\" \"AzManApplicationName\" \"OperationID\"");

                return;

   }

            try

            {

                LogonAdamUser(

                        args[0],

                        args[1],

                        AuthType,

                        args[2], args[3], out UserDN, out UserSid, out TokenGroupSids);

                Console.WriteLine("User Logged on Successfully:");

                Console.WriteLine("UserDN {0} , UserSid {1}", UserDN, UserSid);

               

                // Load AzMan Store

                AzAuthorizationStoreClass AzStore = null;

                AzStore = new AzAuthorizationStoreClass();

                AzStore.Initialize(0, args[4], 0);

                Console.WriteLine("Opened Store:");

                IAzApplication2 AzApp = null;

                AzApp = (IAzApplication2)AzStore.OpenApplication(args[5], null);

                Console.WriteLine("Opened Application:" + AzApp.Name);

                //Create Empty ClientContext

                IAzClientContext2 ClientCon = null;

                ClientCon = (IAzClientContext2)AzApp.InitializeClientContext2("Adam user", null);

                //Add user Sid and group sids to client context

                object[] userSids = new Object[TokenGroupSids.Count + 1]; //Group sids + user sid

               

                // Add UserSid

                userSids[0] = (object)UserSid;

                //AddGroup Sids

                int i = 1;

                foreach (string GroupSid in TokenGroupSids)

                {

                    userSids[i] = (object)GroupSid;

                    i++;

                }

                ClientCon.AddStringSids(userSids);

                Console.WriteLine("Added Adam user sid and group sids to client context.");

                //Set LDAP QueryDN for adam user. This is needed if LDAP query groups are involved.

                ClientCon.LDAPQueryDN = "LDAP://" + args[0] + "/" + UserDN;

             Console.WriteLine("Set LDAPQueryDN on ClientContext:" + ClientCon.LDAPQueryDN);

                //Do AccessCheck

                object[] scope = new Object[1];

                scope[0] = (object)""; //Application Scope

                object[] operations = new Object[1];

                operations[0] = Int32.Parse(args[6]);

                object[] results;

                results = (object[])ClientCon.AccessCheck("Adam User AccessCheck", (object)scope, (object)operations, null, null, null, null, null);

                foreach (int iRes in results)

                {

                    Console.WriteLine("*********************************");

                    if (iRes == 0)

                    {

                        Console.Out.WriteLine("ACCESS GRANTED");

                    }

                    else

                    {

                        Console.Out.WriteLine("ACCESS DENIED");

                    }

                    Console.WriteLine("*********************************");

              }

            }

            catch (Exception Ex)

            {

                Console.WriteLine(Ex.Message);

                Console.WriteLine(Ex.StackTrace);

            }

        }

        /****************************************************************

         *

         * Purpose: Given the userPrincipalName of a user and

         * it's password, retrieve the user's distinguishedName,

         * string sid, and tokenGroups.

         *

         ****************************************************************/

        public static void LogonAdamUser(

                string adamServer,

                string partitionName,

                AuthenticationTypes AuthType,

                string username,

                string password,

                out string userDN,

                out string userSid,

                out ArrayList tokenGroupSids

            )

      {

            string adsPath = null;

            if ((Environment.OSVersion.Version.Major < 5)

                || ((Environment.OSVersion.Version.Major == 5)

                    && (Environment.OSVersion.Version.Minor <= 1)

                   )

       )

            {

                adsPath = "LDAP://" + adamServer;

            }

            else

            {

                adsPath = "LDAP://" + adamServer + "/RootDSE";

            }

           

            //Incase you don't have SSL setup, change the AuthenticationTypes to AuthenticationTypes.None.

            DirectoryEntry entry = new DirectoryEntry(adsPath, username, password, AuthType);

            entry.RefreshCache(new string[] { "tokenGroups" });

            PropertyValueCollection propertyValues = entry.Properties["tokenGroups"];

            Console.WriteLine("Token groups = {0}", propertyValues.Count);

            tokenGroupSids = new ArrayList();

            foreach (object val in propertyValues)

            {

                string stringSid = ConvertSidToStringSid((byte[])val);

                tokenGroupSids.Add(stringSid);

            }

            adsPath = "LDAP://" + adamServer + "/" + partitionName;

            entry.Path = adsPath;

            string filter = "(&(objectClass=user)(userPrincipalName=" + username + "))";

            string[] propertiesToLoad = new string[] { "objectSid", "distinguishedName" };

            DirectorySearcher searcher = new DirectorySearcher(entry, filter, propertiesToLoad, SearchScope.Subtree);

            // UPN has to be unique for authentication to work.

            // So assuming that only 1 entry will be returned.

            SearchResult result = searcher.FindOne();

            if ((result.Properties.Contains("distinguishedName")) && (result.Properties["distinguishedName"].Count > 0))

                userDN = result.Properties["distinguishedName"][0].ToString();

            else

                userDN = null;

            if ((result.Properties.Contains("objectSid")) && (result.Properties["objectSid"].Count > 0))

                userSid = ConvertSidToStringSid(((byte[])(result.Properties["objectSid"][0])));

            else

                userSid = null;

        }

        [DllImport("Advapi32.dll", EntryPoint = "ConvertSidToStringSidW", CharSet = CharSet.Unicode, SetLastError = true)]

        public static extern int ConvertSidToStringSidW(IntPtr pSid, ref IntPtr stringSid);

        [DllImport("kernel32.dll", EntryPoint = "LocalFree")]

        public static extern int LocalFree(IntPtr mem);

        /**********************************************************

         *

         * Purpose: To convert the sid in byte form to string form.

         *

         **********************************************************/

        private static string ConvertSidToStringSid(byte[] sidBytes)

        {

            string stringSid = null;

            IntPtr ptr = (IntPtr)0;

            // Allocate memory for Byte[]

            IntPtr sidPtr = Marshal.AllocHGlobal(sidBytes.Length);

           // Copy byte[] to allocated memory

            Marshal.Copy(sidBytes, 0, sidPtr, sidBytes.Length);

            //Convert sid to string sid

            int result = ConvertSidToStringSidW(sidPtr, ref ptr);

            // Free allocated memory

            Marshal.FreeHGlobal(sidPtr);

            if (result == 0)

            {

                Console.WriteLine("ERROR converting sid to string sid: {0}", Marshal.GetLastWin32Error());

            }

            else

            {

                try

            {

                    stringSid = Marshal.PtrToStringUni(ptr);

                }

                finally

                {

                    LocalFree(ptr);

                }

            }

            return stringSid;

        }

    }

}

 

usage:

AzManADAMAuth "<ADAMServername:Port>" "<PartitionName>" "<UserName>" "<Password>" "<AzManStoreURL>" "<AzManApplicationName>" "<AzManOperationID>"

Following VB script is to add a SID to a Role in AzMan store. Script can be extended to add the SID to any Role\Group in Application\Scope of AzMan store.

 

'Script to Add a SID to Role in AzMan Aapplication

'Can be extended to add the sid to Role/Group in Store/Application/scope

'Usage AddSidToAzMan "StoreURL" "ApplicationName" "RoleName" "SID"

Dim AzSt

Set AzSt = CreateObject("AzRoles.AzAuthorizationStore")

AzSt.Initialize 0, WScript.Arguments(0)

Dim AzApp

Set AzApp = AzSt.OpenApplication(WScript.Arguments(1))

WSCript.Echo "Opened Appplicaton:" & AzApp.Name

Dim AzRole

Set AzRole = AzApp.OpenRoleAssignment(WScript.Arguments(2))

Wscript.Echo "Opened RoleAssignment:" & AzRole.Name

AzRole.AddMember WScript.Arguments(3), 0

AzRole.Submit 0,0

WScript.Echo "Added SID:" & WScript.Arguments(3) & " to Role: " & WScript.Arguments(2)

 

Thanks to ADAM team for providing sample code of ADAM user logon.

 

Thanks,

Sudheer Mamidipaka