# 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.