# Monday, 28 July 2008
I spent quite a while working with ADAM (Active Directory Application Mode) in Windows 2003 Server / XP on a previous project.  It's a great way to incorporate an LDAP directory into your application without requiring a full-blown AD installation, or domain memberships.  The Windows 2008 Server version has been renamed AD LDS (Lightweight Directory Services), which is a much better name.  LDS incorporates several improvements that make it even more attractive, including some very nice performance increases.   Bart De Smet has a very nice guide to installing LDS on 2008 Server and setting up a useful configuration.  Check it out if you've used ADAM and/or have any interested in LDAP. 

Monday, 28 July 2008 12:54:31 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  | 
# Wednesday, 18 April 2007

I'll be moderating a Birds of a Feather session on usage and adoption of ADAM and AzMan for application development on Tuesday night at the BoF evening event.  If you are working on an ADAM/AzMan project, or just considering it, come on down and chat with us about your experiences and/or plans.

ADAM | AzMan | TechEd
Wednesday, 18 April 2007 08:43:30 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, 15 March 2007

Donovan Follette, ADAM/AzMan/Identity Evangelist at Microsoft, has posted a set of links to some videos, whitepapers, and case studies on using AzMan.  Not only are these great resources, but it's great to see Donovan working to make sure that AzMan doesn't stay the "best kept secret" in Windows 2003 Server. 

One of the big questions we've had around using AzMan has been "is anyone actually using this thing?", so it's very cool to get our hands on some real case studies to answer that question in the affirmative.

Thanks Donovan!

Thursday, 15 March 2007 13:57:36 (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 

I'm just starting to look into the feasibility of implementing complex constraints in the world of AzMan.  I've already discovered how easy it is to create simple "accessibility" constraints (i.e. can user x execute operation y), but now we need a solution for what we think of as "parameterized" constraints, like can user x execute the transfer operation if the total amount user x wants to transfer is $y.

AzMan certainly provides the capability, in the for of "BizRules", which are scripts that can be attached to roles and other objects to perform exactly this kind of evaluation.  When your application calls CheckAccess, it can pass a set of parameters to evaluate, as well as additional objects, etc.  I have two main concerns about this that I'm going to spend the next week or so evaluating...

  1. Is this going to give us the performance we need?
  2. Is the interface too cumbersome?

Both of those concerns stem from the fact that the only interface currently provided to AzMan is COM based, which means that I'm forced to write my scripts in VBScript of JScript, and I have to pass COM objects (or at least COMVisible objects) for all my parameters at evaluation time.  That means potentially some extra marshalling overhead, although I think there are ways around that.  It also makes the interface to CheckAccess a bit clunky.

I guess I'll find out...

Thursday, 15 March 2007 13:21:44 (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [3]  | 
# Wednesday, 07 March 2007

It's highly encouraging to me that MS is continuing investment in ADAM.  There have been a number of naming changes for "Longhorn" Server, and ADAM hasn't escaped.  The new name is ADLDS, for Active Directory Lightweight Directory Services.  A much more appropriate name, since it says exactly what ADAM does.  It's the LDAP part, without the NOS bits of AD. 

I've noticed that the name change even shows up in a few places on the Windows 2003 Server site, which is a bit confusing.

Anyway, the take away is that not only is ADAM not going away, but it's a part of the platform that MS is committed to not only supporting but improving.  Does my heart good.

Wednesday, 07 March 2007 10:29:34 (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, 19 February 2007

I'm finally taking the time to get to know PowerShell, and I'm pretty much as impressed as I expected to be given the rave reviews from Scott and others. 

In rewriting our core platform, we're committed to exposing management operations through both PowerShell and MMC, so I've been building out some PS Cmdlets, and now a "drive provider" to make it easy for an end user to navigate through our authentication system.  In the end, the user will be able to "CD" into our authentication system, and navigate the available groups (ADAM) roles, and operations (both from AzMan) like they were directories and files in a file system. 

It's taken me a while to get my head around the PS drive provider model, but now that I (mostly) have, I totally get how flexible a model it is.  You can choose different levels of functionality to support, including adding new items to the tree, "cd"-ing into multiple levels of hierarchy, etc.  Once I grokked the stateless model used by the drive providers, it made sense as an easy way to expose hierarchical data to PS, in a way that's intuitive to users. 

Once that stuff is done, we'll layer an MMC interface on top of the PS Cmdlets, so we won't have to reinvent the wheel functionality wise. 

Very exciting times...

ADAM | AzMan | Work | PowerShell
Monday, 19 February 2007 11:06:24 (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
# Tuesday, 13 February 2007

One obvious thing you might want to do with an authorization system is to get a list of all the operations a given user is granted access to.  This is particularly useful for building up UI elements such as navigation menus.  Nobody likes navigation links that result in Access Denied errors, so it's best to only include in your navigation hierarchy those pages a user can actually execute successfully. 

Here's an example of how to do this with ADAM and AzMan.  It's a little weird, since you have to switch back and forth between an operation's string name, and its integer ID, hence the lookup dictionaries. 

 

   1:  public string[] AvailableOperations(string userId)
   2:  {
   3:      IAuthenticationStore authenticate = AuthenticationStoreFactory.Create();
   4:      UserInfo userInfo = authenticate.LookupUser(userId);
   5:      IAzClientContext2 client = setupClientContext(userId, userInfo);
   6:   
   7:      try
   8:      {
   9:          Dictionary<string, int> opToId = 
  10:      new Dictionary<string, int>(azApp.Operations.Count);
  11:          Dictionary<int, string> idToOp =
  12:      new Dictionary<int, string>(azApp.Operations.Count);
  13:          foreach (IAzOperation op in azApp.Operations)
  14:          {
  15:              opToId.Add(op.Name, op.OperationID);
  16:              idToOp.Add(op.OperationID, op.Name);
  17:              Marshal.ReleaseComObject(op);
  18:          }
  19:   
  20:          List<string> availableOps = new List<string>();
  21:   
  22:          object[] scope = new object[] { (object)"" };
  23:   
  24:          object[] operations = new object[idToOp.Count];
  25:          int i = 0;
  26:          try
  27:          {
  28:              foreach (int opId in idToOp.Keys)
  29:              {
  30:                  operations[i] = (object)opId;
  31:                  i++;
  32:              }
  33:          }
  34:          catch (COMException ex)
  35:          {
  36:              throw new InvalidOperationException("Failed", ex);
  37:          }
  38:          object[] results = (object[])client.AccessCheck("User authentication", scope,
  39:               operations, null, null, null, null, null);
  40:   
  41:          for (int j = 0; j < results.Length; j++)
  42:          {
  43:              if ((int)results[j] == 0)
  44:              {
  45:                  availableOps.Add(idToOp[(int)operations[j]]);
  46:              }
  47:          }
  48:          return availableOps.ToArray();            
  49:      }
  50:      finally
  51:      {
  52:          Marshal.ReleaseComObject(client);
  53:      }
  54:   
  55:  }

I talked about the LookupUser and setupClientContext methods yesterday.  This method returns a handy list of all the operations that the given user can access.  In practice, you probably want to cache the results, although so far it seems to perform very well.  It might prove particularly valuable to cache the ID->Name and Name->ID lookup tables, as they aren't likely to change very often.

Tuesday, 13 February 2007 09:34:22 (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, 12 February 2007

I'm working on an authentication and authorization solution using ADAM and AzMan, and starting to write the code to integrate the two.  For those who might be looking to do such a thing, here's an example.  This method takes two pieces of information, a user's ID (which in this case corresponds to a userPrincipalName in ADAM), and the name of the operation to check access to...

 

   1:  public bool CheckAccess(string operation, string userId)
   2:  {
   3:      log.Verbose("Starting CheckAccess");
   4:      bool result = false;
   5:      IAuthenticationStore authenticate = AuthenticationStoreFactory.Create();
   6:      UserInfo userInfo = authenticate.LookupUser(userId);
   7:      log.Verbose("LookupUser {0} succeeded", userId);
   8:      IAzClientContext2 client = setupClientContext(userId, userInfo);
   9:   
  10:      object[] scope = new object[] {(object)""};
  11:   
  12:      object[] operations = null;
  13:      try
  14:      {
  15:          IAzOperation op = azApp.OpenOperation(operation, null);
  16:          operations = new object[] { (Int32)op.OperationID };
  17:          Marshal.ReleaseComObject(op);
  18:      }
  19:      catch (COMException ex)
  20:      {
  21:          log.Error("No such operation: {0}", operation);
  22:          if (ex.ErrorCode == AzManConstants.INT_NO_SUCH_OBJECT)
  23:              throw new UnknownOperationException(operation);
  24:          else
  25:              throw;
  26:      }
  27:      object[] results = (object[])client.AccessCheck("User authentication", scope, 
  28:                      operations, null, null, null, null, null);
  29:      if ((int)results[0] == 0)
  30:      {
  31:          result = true;
  32:          log.Verbose("Access granted to {0} for user {1}", operation, userId);
  33:      }
  34:      else
  35:      {
  36:          log.Verbose("Access denied to {0} for user {1}", operation, userId);
  37:      }
  38:      Marshal.ReleaseComObject(client);
  39:      log.Verbose("Leaving CheckAccess");
  40:      return result;
  41:  }
 

There are a couple things here that need explaining.  The LookupUser method finds the specified user in ADAM by searching for the userPrincipalName using the DirectorySearcher class.  It returns a structure containing the user's SID, all of the SIDs for groups they belong to, and some other stuff specific to our application.  There's a great example of how to retrieve this info in the paper Developing Applications Using Windows Authorization Manager, available on MSDN. 

The other method of interest here is the setupClientContext method, which looks like this...

 

   1:  private IAzClientContext2 setupClientContext(string userId, UserInfo userInfo)
   2:  {
   3:      log.Verbose("Setting up client context");
   4:      IAzClientContext2 client = azApp.InitializeClientContext2(userId, null);
   5:      log.Verbose("User sid = {0}", userInfo.SecurityId);
   6:      object[] userSids = null;
   7:      if (userInfo.GroupIds != null)
   8:      {
   9:          userSids = new object[userInfo.GroupIds.Count + 1];
  10:          userSids[0] = userInfo.SecurityId;
  11:          int i = 1;
  12:          foreach (string sid in userInfo.GroupIds)
  13:          {
  14:              log.Verbose("Adding group sid {0}", sid);
  15:              userSids[i] = sid;
  16:              i++;
  17:          }
  18:      }
  19:      else
  20:      {
  21:          log.Verbose("No group IDs returned, using only user SID");
  22:          userSids = new object[1];
  23:          userSids[0] = (object)userInfo.SecurityId;
  24:      }
  25:      client.AddStringSids(userSids);
  26:      log.Verbose("Finished setting up client context");
  27:      return client;
  28:  }
 
 

This is the part that only works on Windows 2003 Server or Vista. On XP, the IAzClientContext2 interface is not available, and so you can't add the additional group SIDs to make this work.

The end result is that the user's role membership will be used by AzMan to determine whether or not the specified user can access the specified operation, and return a boolean value back to the caller.  Easy to use by the client, and as far as I can tell, pretty performant.

Monday, 12 February 2007 11:21:02 (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
# Wednesday, 24 January 2007

I'll post some code later on, but I wanted to make some quick points about integrating ADAM and AzMan.  I'm in the midst of building an authentication/authorization system using the two technologies together, and have some across some stumbling blocks.  There's not much documentation, particularly around AzMan, and the COM interfaces for AzMan can be a bit cumbersome.

  • Storing users in ADAM and authorizing them using ADAM requires Windows 2003 Server or Vista.  There's no decent way to make this work on Windows XP.  The necessary AzMan interface, IAzClientContext2, doesn't exist on XP.  It's required for using a collection of user and group SIDs from ADAM to do access checks against AzMan.  I'll post some code later...
    • IAzClientContext2 is also available on Vista, so Vista is also a viable dev platform.
  • There are some confusing interactions between the AzMan UI and the programmatic API.  If you create a Role in the AzMan UI, but don't create a RoleAssignment, the programmatic call to IAzApplication2.OpenRole will fail.  If you create the role assignment, but don't actually assign any users or groups to it, OpenRole succeeds.  Conversely, if you call the programmatic IAzApplication2.CreateRole method and assign operations and users to the role in code, the RoleAssignment shows up in the UI, but not the Role itself. 
  • If you assign an ADAM user to be a member of an AzMan group, it won't show up in the AzMan UI, but if you assign them directly to a Role, the ADAM user's SID will show up (as "unknown SID") under the RoleAssignment.  Either way, the call to AccessCheck works correctly.
  • You must pass the complete list of group SIDs from ADAM, but fetching the user's "tokenGroups" property.  Don't use "memberOf" because it doesn't take into account groups which belong to other groups.

More detail to come...

Wednesday, 24 January 2007 10:08:12 (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [2]  | 
# Tuesday, 17 October 2006

After some minor setbacks, I’m finally getting going with ADAM, and amazed at how well it works (once you figure out how it’s supposed to work, which is a non-trivial undertaking).  One thing that makes it harder is that most of the (still scant) documentation around ADAM is geared toward IT Pros, not developers, so there are plenty of places where you track down the documentation for that critical change you need to make to your directory, only to be told “start the command line tool…”.  Frustrating, but workable. 

Once the magic incantations are prized forth, it seems very fast.  Granted we’re not doing anything tricky at this point, but adding users, setting passwords, simple queries, all seem pretty snappy. 

On my previous problems with ADAM and AzMan integration, this is apparently caused by the fact that I had my users in one application partition in ADAM, and my AzMan store in another partition in the same ADAM instance.  The official word is that this is not supported, which probably makes sense.  It means that I’ll probably end up using a separate ADAM instance as the AzMan store, or fall back to the XML file, although that’s harder to distribute.  Only time will tell.

Tuesday, 17 October 2006 10:32:08 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  | 
# Friday, 06 October 2006

I'd been having some trouble (read: beating my head against the wall) trying to get passwords set/changed for users in ADAM early this week.  Unfortunately, many of the available samples are geared toward IT rather than development users, so tend to be in script, etc.  I've been working from the excellent book "The .NET Developer's Guide to Directory Services Programming" by Joe Kaplan and Ryan Dunn.  Sadly, I couldn't get their password changing samples to work, although aside from that the information in the book has been invaluable. 

I finally found what I needed in an obscure section of the documentation after many a googling.  This article gave me the key info that made this work.  I haven't tried all the permutations, but I think the key piece I was missing was the right combination of AuthenticationTypes flags

 

            // Set authentication flags.

            // For non-secure connection, use LDAP port and

            //  ADS_USE_SIGNING |

            //  ADS_USE_SEALING |

            //  ADS_SECURE_AUTHENTICATION

            // For secure connection, use SSL port and

            //  ADS_USE_SSL | ADS_SECURE_AUTHENTICATION

            AuthTypes = AuthenticationTypes.Signing |

                AuthenticationTypes.Sealing |

                AuthenticationTypes.Secure;

 

            // Bind to user object using LDAP port.

            try

            {

                objUser = new DirectoryEntry(

                    strPath, null, null, AuthTypes);

                objUser.RefreshCache();

            }

            catch (Exception e)

            {

                Console.WriteLine("Error:   Bind failed.");

                Console.WriteLine("        {0}.", e.Message);

                return;

            }

This sample worked just fine, and I was finally able to set or change passwords for ADAM users.  Once that's done, authenticating that user is as easy as

            DirectoryEntry de = new DirectoryEntry("LDAP://localhost:50000/RootDSE",

                "test.user",

                "passwordSuppliedByUser",

                AuthenticationTypes.None);

            de.RefreshCache();

If the supplied password is correct, it succeeds, else you get an exception. 

Now I can get on with the interesting parts...

Friday, 06 October 2006 10:42:25 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  | 
# Tuesday, 03 October 2006

I’m sure this must work for someone, but I’m having a heck of a time with ADAM.  The latest is that I can create users just fine, assign properties to them, etc., but I cannot seem to set their passwords.  I feel like I’m missing something here, possible a permissions problem, although I can set passwords through the ADSI GUI.  The other possibility is that the user is not in the right state, maybe not correctly enabled, or something.  This is the code that should work

            entry = new DirectoryEntry("LDAP://localhost:50000/CN=test.user,OU=Users,C=Bank", null, null, AuthenticationTypes.Secure);

            entry.RefreshCache();

            entry.Options.PasswordEncoding = PasswordEncodingMethod.PasswordEncodingClear;

            entry.Options.PasswordPort = 50000;

 

            entry.Invoke("ChangePassword", new object[] {"old","new"});

Unfortunately, I have no idea what the problem really is, because all I get back is

System.Reflection.TargetInvocationException was unhandled
  Message="Exception has been thrown by the target of an invocation."
  Source="System.DirectoryServices"
  StackTrace:
       at System.DirectoryServices.DirectoryEntry.Invoke(String methodName, Object[] args)
       at AdAzMan.Program.Main(String[] args) in Program.cs:line 43
       at System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()

Where the inner exception is {"An operations error occurred. (Exception from HRESULT: 0x80072020)"}.  Not very helpful.  That’s pretty much equivalent to “something bad happened”.  Granted, it’s a different error from the ones I get if the bind fails, or if I don’t set the entry’s password options to allow changing the password over an insecure channel, but this one’s got me stymied.

Tuesday, 03 October 2006 10:33:39 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, 10 August 2006

I have progressed a bit.  At least I seems to have an install where ADAM and AzMan will coexist happily in the same ADAM instance, and I can retrieve a user from the ADAM store.  I can also add roles to AzMan programatically, so that's all good.  However, I still can't add an ADAM principal to AzMan as a member of a role.
This is supposed to work...

   15           string roleName = "RetailUser";

   16 

   17             MembershipUser user = Membership.GetUser("TestUser@bank.com");

   18             Console.WriteLine(user.ProviderUserKey);

   19 

   20             IAzAuthorizationStore2 azStore = new AzAuthorizationStoreClass();

   21             azStore.Initialize(0, "msldap://localhost:50000/CN=Test,CN=AzMan,O=AzManPartition", null);

   22             IAzApplication2 azApp = azStore.OpenApplication2("TestApp", null);

   23 

   24             IAzTask task = azApp.CreateTask(roleName, null);

   25             task.IsRoleDefinition = -1;

   26             task.Submit(0, null);

   27             IAzRole role = azApp.CreateRole(roleName, null);

   28             role.AddTask(roleName, null);

   29             role.Submit(0, null);

   30 

   31             IAzRole newRole = azApp.OpenRole(roleName, null);

   32 

   33 

   34             newRole.AddMember(user.ProviderUserKey.ToString(), null);

   35             newRole.Submit(0, null);

And should result in TestUser@bank.com being added to the role "RetailUser". 
Sadly, on that last line, I get

System.ArgumentException was unhandled
  Message="Value does not fall within the expected range."
  Source="Microsoft.Interop.Security.AzRoles"
  StackTrace:
       at Microsoft.Interop.Security.AzRoles.IAzRole.Submit(Int32 lFlags, Object varReserved)
       at ADAMAz.Program.Main(String[] args) in C:\Documents and Settings\PCauldwell\My Documents\Visual Studio 2005\Projects\ADAMAz\ADAMAz\Program.cs:line 35
       at System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()

All I can figure is that AzMan doesn't like the SID as generated above. 
I'm running this on XP SP2, with the 2003 Management tools, and ADAM SP1 installed.  I'm fearing that I may have to run this on 2003 R2 to get it to work.


Thursday, 10 August 2006 17:07:15 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |