Authorization Manager Team Blog

  • Vista Features Demo

    FYI,

    Just posted: the final session on AzMan by Keith Brown that demonstrates some of the new features of AzMan in LHS and Vista.

                    Blog Post: http://blogs.msdn.com/donovanf/

                    Channel9 Screencast: http://channel9.msdn.com/Showpost.aspx?postid=298350

    -Dave

  • BizRules opt-in

    Heads up to a change on Vista; if you get something like this error:

    Business rule scripts are disabled for the calling application. (Exception from HRESULT: 0xC0090003)

    You're experiencing a new default for AzMan in Vista and beyond. That is, BizRules are opt-in now. Each application can say whether or not it wants to allow BizRules to execute. This setting is on the machine not on the store.

    To enable BizRules for an application run the following script:

    '

    '  Enabling or disabling BizRules for an application

    '  This script uses Authorization Manager Administrative interfaces to enable or disable

    '  BizRules for a specified AzMan application in a specified AzMan policy store

     

    On Error Resume Next

     

    Set objArgs = WScript.Arguments

     

    If objArgs.count <> 3 then

      wscript.echo "Usage: SetBizRule ""AzManStoreURL"" ""AzApplicaitonName"" True/False"

      wscript.echo "Example: SetBizRule ""msxml://d:\inetpub\wwwroot\AzStore.xml"" ""MyApp"" True"

      wscript.echo "Run with 'cscript' command in cmd.exe to avoid msg boxes"

      WScript.Quit

    Else

     

     

      ' VBScript source code

      Dim AzStoreObj

      Dim AzManStoreURL : AzManStoreURL = objArgs(0)

      Dim AzManAppName : AzManAppName = objArgs(1)

      Dim BizRulesEnabled : BizRulesEnabled = objArgs(2)

     

     

      ' create azman object

      Set AzStoreObj = CreateObject("AzRoles.AzAuthorizationStore")

     

      If Err.Number > 0 Then

        WScript.Echo "Can not create AzRoles.AzAuthorizationStore. Check AzMan installation"

        WScript.Quit

      End If

     

      ' initialize store for Administration

      ' assumes store exists - if store is being created (e.g. an installing applicaion)

      ' use the value 3 instead of 2 in the call to IAzAuthorizationStore::initialize

     

      Err.Clear

     

      AzStoreObj.Initialize 2, AzManStoreURL

     

      If Err.Number <> 0 Then

        WScript.Echo "AzRoles.AzAuthorizationStore failed to initialize. Check store URL"

        WScript.Quit

      End If

     

      ' open applicaion

      set AzApp = AzStoreObj.OpenApplication(AzManAppName)

      If Err.Number <> 0 Then

        WScript.Echo "AzRoles.AzAuthorizationStore failed to open application: " + AzManAppName + ". Check application Name."

        WScript.Quit

      End If

     

      ' set BizRulesEnabled property

      WSCript.Echo "App BizRule Before:" & AzApp.BizRulesEnabled

      AzApp.BizRulesEnabled = BizRulesEnabled

      WSCript.Echo "App BizRule After:" & AzApp.BizRulesEnabled

     

      If Err.Number = 0 Then

        WScript.Echo "BizRulesEnabled is updated successfully."

      Else

        WScript.Echo "BizRulesEnabled is NOT updated successfully."

      End If

     

    End if

     

    This info is in the online help but I figured some folks may not look there.

     

    -Dave

  • AzMan Videos

    The MS Developer and Platform Evangelism team has produced a series of AzMan developer Web Casts hosted by Keith Brown, check out the first of the four part series:

    http://channel9.msdn.com/Showpost.aspx?postid=289062

     

    Watch Donovan’s blog for news on the rest of the series; http://blogs.msdn.com/donovanf/

     

     

    Here’s some additional video resources I’ve been remiss in not listing.

    ADFS/AzMan .Net Show

    A discussion on ADFS and Authorization Manager

    http://msdn.microsoft.com/theshow/episode.aspx?xml=theshow/en/Episode047/manifest.xml

     

     

    ISV Chalk Talk – Windows Authorization Manager

    An introductory interview about AzMan.

    http://www.microsoft.com/downloads/thankyou.aspx?familyId=4773b5c8-e9ee-49a8-81c9-2f3bd3994f3a&displayLang=en

     

    Enjoy,

    -Dave

  • Impersonation Shenanigans?

    Ever have different behavior when connecting to an application sever remotely vs. locally? Here’s a common gotcha than can be tough to unwind. This is described here in an AzMan scenario but any app server that makes any network calls can have similar problems with the same root cause.

    Sometimes developers see that they have different access results depending on whether they hit an app server locally vs. remotely. For example, when you use the same box that the app server is running on to browse to a web page of the app server everything works; but when you use a browser on a different machine to browse to the app server you don’t have access to some resource (or get other weird problems.) This is a common road bump when developing a service that is impersonating the client and then doing a query to another machine (such as a DC) as the client.

    This difference is because when a local logon (a logon that was done on the same machine as that which the app server is running on) authenticates to the application server the local logon token is usually reused for the logon token to the application server. If this token is then impersonated when a query to a remote machine is done then the call will authenticate to the remote machine as the client. The local logon allows the client to be authenticated to the remote machine because a local (interactive) logon has a password (or some credential) associated with it and therefore is capable of authenticating to another machine. For a remote client (using integrated auth) a "network" token is usually created on the web service machine; it will not have a credential associated with it so it cannot make a second hop as a client who authenticated to the application server (that would require delegation which takes more setup.) In this case the remote call will authenticate to the remote machine as the app server account.

    One way to catch this is if you can monitor the connections on the machine you’re making the impersonated query to (this remote query may be done in an API that reads from another machine such as a DC) and you can check who is connecting.  If you’re connecting as the machine account of the app server then the impersonation did not carry through the impersonated call to the remote machine.

    If you think you’re running into this situation and for some reason you want to maintain the impersonation to the back-end machine lookup Kerberos delegation on MSDN / TechNet, but In AzMan scenarios it’s probably not the way to fix this. AzMan apps typically use a trusted-subsystem model in which you don’t impersonate the client and access resources but instead access resources as the app server account. See the AzMan Dev Whitepaper for more on trusted-subsystem.

    In AzMan people hit this problem when accessing the DC via the InitializeClientContextFromName call or at some point during authorization validation (AccessCheck etc.) To avoid this in AzMan make sure your not impersonating (unless you really want to for some reason) and then make sure the app service account has access to the back-end resource s the app needs to access. For AzMan apps that’s typically the authorization store. And if you’re using InitializeClientContextFromName the app server account need access to read user objects in AD.

    To access the AzMan policy store you need to give the app server account at least read access to the store (see the AzMan Dev Whitepaper for info on that.)

    To give the application server account access to the user objects (if you’re using InitializeClientContextFromName) put the web service account in the Windows Authorization Access Group on the DC (or the Pre-Win2k compatibility Access Group; that grants broader than necessary read permissions in the domain but it avoids some down-level cases that membership in the WAA group does not.) See http://support.microsoft.com/kb/331951 for in-depth info on granting access to the user tokenGroupsGlobalAndUniversal attribute.)

    And as always, if you’re using InitializeClientContextFromName use InitializeClientContextfromToken if at all possible.

     

    -Dave McPherson

  • New AzMan Developer Whitepaper

  • AD or ADAM?

    A common question among AzMan deployments is how to pick the domain to store the AzMan policy in or whether or not to use ADAM. This comes down to factors that are unique to the network topology and applications but here are some initial suggestions to help decide (note that in Vista you'll have the additional option of using SQL.)

     

    If your questioning whether or not you should put something in Active Directory use the best practice of putting data into the directory if it's globally interesting and relatively static. In most scenarios authorization policy is relatively static so the question becomes how globally interesting is it. If your authorization policy is used by many applications (or application instances) in a particular forest or domain then the directory is the ideal place to put it. The benefit of using Active Directory is that you leverage the infrastructure investment that you have already made for reliability and availability. So generally, when deciding which AD domain to use to store AzMan policy or whether to use AD or ADAM you should first look to leverage AD, look for a domain that's a good fit for the applications using the policy and then to ADAM if AD doesn't fit. There are several reasons it may not fit for a particular deployment.

     

    In picking the domain, ideally you want the AzMan applications that need the authorization policy to be able to quickly get it from nearby DCs. Meanwhile, those DCs not near AzMan applications should avoid replication of the AzMan store. Since the AzMan policy will be replicated to each DC in the domain in which it is stored it's possible that if you select a broad domain that the AzMan policy would be replicated to DCs where no applications are using that policy. The ideal scenario for using the AD policy store is when the authorization policy is being used by apps nearby the DCs in a domain. When a domain has a high number of DCs that would not be used by AzMan apps or administrators to access the AzMan policy then it's worth it to try and optimize the deployment by using an existing convenient child domain, creating a child domain, or use an ADAM store.

     

    When there are multiple domains then it is often the case that an existing domain has its DCs already positioned near the set of apps using the same AzMan policy store (related apps are often in the same domain.) When this is the case such a domain is a good place to store the AzMan policy. If the applications are in different domains and the applications are unrelated in terms of administration then you may be better off with separate stores. In this case the stores may be AD, ADAM or XML depending on the needs of the app and administrators.

     

    An ADAM store is ideal if there is IT resistance to adding an AzMan policy store to a domain or resistance to creating a domain for the sole purpose of storing Authorization Manager policy. When this happens ADAM is ideal to store the policy for one or more AzMan applications. Additionally, if the application already uses ADAM for some purpose then using ADAM to store the policy may make the overall setup and administration simpler.

     

    The other (more obvious) scenario where ADAM is used is for situations where you can't know for sure if a domain will be available (from the ISV perspective) or when your organization doesn’t have a domain. In these situations it pretty easy set up an ADAM instance with or near your app and store policy there.

     

    This always leads to the question of application partitions. We are investigating the possibility of supporting AzMan deployments in application partitions.

     

    -Dave

  • How come when I create a Role Definition in the UI it doesn't show up when I enum Roles?

    Cbekarthik's question is so common that it deserves its own post.

     

    One quirk of the AzMan dev experience is the fact that the UI exposes a role definition object and the API does not. As a result it's a common surprise for developers who create a role definition in the UI and then see that the code they wrote code to enumerate roles doesn't return any role definitions. The opposite surprise comes when you programmatically create a role (the AzRole object) and then look in the role definitions container in the UI and don't see that role.

     

    The reason behind this is that beta versions of the AzMan interfaces (in the WS03 beta timeframe) had a smaller RBAC model that didn't have the concept of role definitions and role assignments. Roles were defined and assigned in one object called a role. Some late user experience testing made it clear that a role definition was a useful concept to enable the collection of permissions for a role to be reused in different scopes. A good example of when this is useful is a situation such as folders where the role definition of an editor or reader would be useful in all folders even though the assignees would be different in each folder. Based on this feedback the role definition object was added in the UI.

     

    However, since this was very late in the beta process the change in the API had to be minimal (to minimize impact on beta customers, test matrices, and documentation.) So the implementation of the role definition took advantage of the fact that the properties of the desired role definition were essentially the same as those of a task; a collection of tasks and operations. In fact the role definition is just a special task implemented as an IAzTask object with a property called IsRoleDefinition that identifies it as a role definition. So when you create a role definition in the AzMan MMC UI, in the store your creating an IAzTask object with the IsRoleDefinition property set to true. When you create a role assignment in the UI you're creating an IAzRole object.

     

    Since this has been a common hurdle for many who are new to AzMan development, in Vista and LH Server role definition specific interfaces are added so the developer experience will more closely match the user experience; a role definition in the UI will be correspond to a role definition in the API.

     

    You can check out these new interfaces as of Vista beta 1.

     

    -Dave McPherson

  • Query for a User's Roles

    Some folks have asked about doing a query of  a given user's roles. While this is not yet in the UI it is pretty easy to do via script. Here's a sample, if you're integrating AzMan interfaces into your custom UI this logic could be used to implement a user role query across a store.

    ' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
    ' ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
    ' THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
    ' PARTICULAR PURPOSE.
    '
    '  Copyright (c) Microsoft Corporation. All rights reserved
    '
    '
    ' Script to query and dump a users roles in a specified AzMan
    ' store across all applications in the store
    '

    Option Explicit
    Dim objArgs
    Set objArgs = WScript.Arguments
    If objArgs.count <> 2 then
      wscript.echo "Usage: GetRoles <AzManStoreURL> <UserName>"
      wscript.echo "Example: SetBizRule msxml://c:\AzStore.xml nwtraders\JohnDoe"
      wscript.echo "Run in'cscript' command in cmd.exe to avoid msg boxes"
    Else
    Dim AzManStoreURL : AzManStoreURL = objArgs(0)
    Dim UserName: UserName = objArgs(1)
    End If

    '
    '--- Initilaize the Authorization Manager store object
    '
    Dim pAzManStore
    Set pAzManStore = CreateObject("AzRoles.AzAuthorizationStore")
    pAzManStore.Initialize 0, AzManStoreURL
    pAzManStore.Submit

    '
    '--- Dump a users roles
    '
    Dim Apps,App
    Dim ClientContext
    Dim ClientScopes,Scope
    Dim CurrentScopesPage
    Dim ClientRoles, Role
    Dim MoreScopes


    '
    '--- For each app create a clientcontext and enumerate roles in scopes
    '
    Set apps = pAzManStore.Applications

    wscript.echo ("Roles for " & UserName)
    for each app in apps

       Set ClientContext = app.InitializeClientContextFromName(UserName)
       wscript.echo (vbnewline & "Application: " & app.name)
      
       Set CurrentScopesPage = nothing
       Set ClientScopes = nothing
       MoreScopes = True

       do while MoreScopes = True       
          ClientScopes = ClientContext.GetAssignedScopesPage(0,9,CurrentScopesPage)

          for each scope in ClientScopes
             If scope = "" then
                wscript.echo ("  Applicaiton Level Roles:")
             Else
                wscript.echo ("    Scope '" & scope & "' Roles:")
             End if

             ClientRoles = ClientContext.GetRoles (scope)
             for each role in ClientRoles
                wscript.echo ("      " & role)
             next
          next

          if UBound(ClientScopes) = -1 then
             MoreScopes = FALSE
          End If
       loop
    next

  • 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 ((res