# Tuesday, 26 June 2007

A while back I posted about how we're implementing denials using AzMan.  This week, a helpful reader pointed out that with a little extra math, I could save a call into AzMan (with all the attendant COM interop, etc.).

So, instead of my previous

 

object[] grantOps = new object[] { (object)1, (object)2, (object)3, (object)4};

object[] denyOps = new object[] { (object)100001, (object)100002, (object)100003, (object)100004 };

 

 

object[] grantResults = (object[])client.AccessCheck("check for grant", scopes, grantOps, null, null, null, null, null);

object[] denyResults = (object[])client.AccessCheck("check for deny", scopes, denyOps, null, null, null, null, null);

 

for (int i = 0; i < grantResults.Length; i++)

{

    if(((int)grantResults[i] == 0) && ((int)denyResults[i] != 0))

        Console.WriteLine("Grant");

    else

        Console.WriteLine("Deny");

}

 

I can do it in one call like so

object[] allOps = new object[8];

grantOps.CopyTo(allOps, 0);

denyOps.CopyTo(allOps, 4);

object[] allResults = (object[])client.AccessCheck("check for grant and deny", scopes, allOps, null, null, null, null, null);

for(int j = 0; j < grantOps.Length; j++)

{

    if (((int)allResults[j] == 0) && ((int)allResults[j + grantOps.Length] != 0))

    {

        Console.WriteLine("Grant");

    }

    else

        Console.WriteLine("Deny");

}

 

It does mean you have to be a little careful about getting the indexes straight, but that's probably worth the savings you'll get by not making the extra COM call. 

Every little bit helps...

AzMan | Work
Tuesday, 26 June 2007 09:12:39 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [3]  | 
# Thursday, 26 April 2007

Back in February, I mentioned that I was having problems testing our AzMan code on developer machines running Window XP instead of Windows 2003 server.  This was because the 2003 Server version of the AzMan interfaces weren't present on XP.  To deal with the issue, we actually put some code in our unit tests that reported success if running on XP when the error was encountered, so that everyone could have a successful build.  Drag.

Anyway, it turns out I wasn't the only one having this problem.  Helpful reader Jose M. Aragon says

We are trying to get AzMan into our authorization architecture, and were able to do it nicely, as soon as the development was done on Windows Server 2003. But, when we tried to use IAzClientContext2.GetAssignedScopesPage on a developers machine running XP sp2 it fails miserably with a type initialization. Searching on help, I found your entry. so we started to circumvent the problem by programming our own "GetAssignedScopes" method.

Meanwhile, on another XP workstation, the problem was not occurring. So, it turns out the difference resides in what was the Server 2003 admin pack package installed on every XP machine..

adminpak.exe sized 12.537KB on 24 Jan 2007 : Does NOT include IAzClientContext2 on azroles.dll

WindowsServer2003-KB304718-AdministrationToolsPack.exe sized 13.175KB on 13 March 2007 : allows using IAzClientContext2 on XP

It seems the two files where downloaded from microsoft.com/downloads, but the one on Server2K3 SP1 is the most recent AdminPak.

I uninstalled my original version of the Admin Pack on my XP-running laptop, installed the new one, and now all of our tests succeed without the hack!

Thanks Jose!

Thursday, 26 April 2007 13:02:04 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  | 
# 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]  | 
# Tuesday, 17 April 2007

I've been wrestling for a while with how to deal with the fact that AzMan doesn't natively have a concept of denials.  I'd convinced myself that you could structure a hierarchy of grants such that you wouldn't really need an explicit denial, but I got a use case last week that proved that it really is necessary (without doing something REALLY horrible). 
I started with some suggesting from Cam, and modified it a bit into something that I think will work going forward.
The basic idea is that for every operation we want to add grants for, e.g. "GetAccounts", we also add a hidden operation called "!GetAccounts".  To keep them separated, we also offset their AzMan operation IDs by some arbitrary constant, say 100,000.  They when we do access checks, we actually have to call IAzClientContext.AccessCheck twice, once for the grant operations and once for the deny operations.  The results get evaluated such that we only grant access for an operation if the access check succeeds for the "grant" operation, AND doesn't succeed for the "deny" operation. 
 
WindowsIdentity identity = WindowsIdentity.GetCurrent();
 
IAzClientContext client = app.InitializeClientContextFromToken((ulong)identity.Token.ToInt64(), null);
object[] scopes = new object[] { (object)"" };
 
object[] grantOps = new object[] { (object)1, (object)2, (object)3, (object)4};
object[] denyOps = new object[] { (object)100001, (object)100002, 
     (object)100003, (object)100004 };
 
 
object[] grantResults = (object[])client.AccessCheck("check for grant", 
     scopes, grantOps, null, null, null, null, null);
object[] denyResults = (object[])client.AccessCheck("check for deny", 
     scopes, denyOps, null, null, null, null, null);
 
for (int i = 0; i < grantResults.Length; i++)
{
       if(((int)grantResults[i] == 0) && ((int)denyResults[i] != 0))
            Console.WriteLine("Grant");
       else
            Console.WriteLine("Deny");
}

We'll hide all the details in the administration tool, so that all the user will see is radio buttons for Grant and Deny for each operations.

I'm pretty happy with this for a number of reasons

  • it gives us the functionality we need
  • it's easy to understand (no coder should have a hard time figuring out what "!GetAccounts" means)
  • all of the logic is localized into the admin tool and the CheckAccess method, so the interface to the client doesn't change
  • it's not too much additional semantics layered on top of AzMan, and we take full advantage of AzMan's performance this way
Tuesday, 17 April 2007 15:44:12 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [3]  | 
# Thursday, 05 April 2007

Donovan posted a link to the latest Channel 9 screencast on AzMan, in which Keith Brown talks about some of the new features coming up in the Longhorn Server version of AzMan.  It's a good video, and very encouraging to me, because it looks like the things the AzMan team has added for Longhorn are exactly the things I would have wanted. 

The programmatic interface is much simplified (thank you) and there's a new plugin model for the admin tool which allows you to work with custom principles such as ADAM principals from within the AzMan MMC snapin. 

Another interesting feature calls BizRule groups allows you to define ad hoc groups defined by script, which can then be associated with roles at runtime.  The example in the video created a group consisting of the current user's direct manager, and then assigning that group with an expense approver group.  This is a cool idea, but I'm reluctant to go back to defining application logic in VB script.  The one thing I really wish the team had done for Longhorn would be to create a managed interface for AzMan, and let us get away from COM (finally).  Since I'm writing the rest of my application in C#, I don't really want to have to maintain and deploy a bunch of COM based scripting files.  I'm not too concerned about the "COM overhead" involved with calling AzMan interfaces, but the scripting part is lame.

I'm looking forward to testing some of the new features, and trying out the performance improvements.

Thursday, 05 April 2007 10:32:08 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  | 
# Wednesday, 04 April 2007

Since there's not way to manage an explicit "Deny" in AzMan like there is with ACL's, that has some influence on the way in which we will have to define roles.  Let's say I have a group hierarchy like this...

  • Ray's Bakery
    • Admins
    • Bill Payers
    • Corporate Headquarters
      • Finance
      • Payroll

If we have the ability to deny, we might give rights to all operations to the top level "Ray's Bakery", and then deny rights to "Payroll" for example that we don't want that group to have, but are OK for "Finance".  It's relatively easy to understand, and has the advantage of being not very much work. 

Because of the way AzMan works, however, we have to work from the top of the hierarchy down, going from most restrictive to least restrictive.  In other words, we can only assign rights to the "Ray's Bakery" group that we want ALL users to have, then grant additional rights as we go down the tree.  We might want everyone in Finance and Payroll to be able to see balances, so those rights could be assigned at the "Corporate HQ" level, while rights to transfer money from one corporate account to another might be granted only to Finance, and rights to wire transfers only to Payroll.  This should also be pretty easy to understand, but it does require a bit more thinking about.  I think a good test tool will help make it clear to users who has what rights, so it shouldn't be too hard to follow. 

For the sake of clearing up any implementation details, what I'm really describing are hierarchical groups in ADAM, which are associated with AzMan roles, so when I say "assign rights at the Finance level", I mean that the group SID for the Finance group in ADAM will be associated with an AzMan role called something like RaysBakeryFinance, which is in turn associated with the operations or tasks that comprise wire transfers. 

Wednesday, 04 April 2007 13:51:28 (Pacific Daylight Time, UTC-07:00)  #    Disclaimer  |  Comments [3]  | 
# Monday, 26 March 2007

A very common banking scenario is one in which Customer Service Reps (CSRs) need to act on behalf of a given user to help them through problems.  This usually occurs when a customer calls a bank, and the CSR needs to essentially log into the online banking system "as" that customer, but without knowing that user's password. 

Once they are logged in, there are some things you'd like them NOT to be able to do, like create a new bill payment payee and send bill payments to it.  'Cause that's bad.

I'm trying to figure out how that would be modeled in AzMan, since as near as I can figure there's not such thing as an explicit "deny" in AzMan.  All rights are essentially additive, and if any of a user's roles includes a grant, access is granted.  At least that's the way it seems to behave, I could be wrong. 

In a perfect world, we'd like to be able to simply model "the CSR can do everything the user can, except...". 

The only way I can figure to make this work in AzMan right now is to create the CSR role with access to all operations except those on the deny list, then when the CSR logs in, do restrict the AccessCheck against AzMan to only the CSR role, by setting the IAzClientContext's RoleForAccessCheck property.  That way, only the CSR's rights are evaluated, even if they are assigned to other roles as well. 

Not ideal, but at least it's easy to understand...

Monday, 26 March 2007 14:40:32 (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [2]  | 
# 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]  | 
# Monday, 26 February 2007

It looks like the issue I've been having with calling AzMan via PowerShell via MMC have nothing to do with PowerShell, and is probably an AzMan issue.  I tried exactly the same scenario, but accessed a different path with my drive provider that ultimately makes a COM call to ADAM, rather than AzMan, and it worked just fine.  That makes me think it's something about the AzMan implementation. 

However, I still need to call my PowerShell drive provider from MMC, and that problem has been resolved, (many) thanks to Bruce Payette

Turns out that there's a way to get PowerShell to run in the caller's thread, rather than creating its own MTA thread.  This means that my PowerShell code will get called from MMC's STA thread directly, and the AzMan interfaces get created just fine.  Woohoo!

Previously, I was creating the Runspace and calling it thusly...

RunspaceConfiguration config = RunspaceConfiguration.Create();
PSSnapInException warning;
//load my drive provider
PSSnapInInfo info = config.AddPSSnapIn(snapInName, out warning); 
Runspace rs = RunspaceFactory.CreateRunspace(config);
rs.Open();
...
create Command object with right parameters
...
Collection<PSObject> results;
using (Pipeline pipeline = _runspace.CreatePipeline())
{
    pipeline.Commands.Add(command);
    results = pipeline.Invoke();
}

This wasn't working, I believe because MMC is calling this code on an STA thread, and PowerShell is creating its own MTA thread, which doesn't seem to be acceptible to AzMan (not sure why).

However, according to Bruce's suggestion, I changed it to look like this...

Runspace rs = RunspaceFactory.CreateRunspace();
rs.Open();
PSSnapInException warning;
//load my drive provider
rs.RunspaceConfiguration.AddPSSnapIn(snapinName, out warning);
Runspace.DefaultRunspace = rs;
EngineIntrinsics _exec = rs.SessionStateProxy.GetVariable("ExecutionContext") as EngineIntrinsics;
ScriptBlock sb = _exec.InvokeCommand.
    NewScriptBlock("my script block");
Collection<PSObject> results = sb.Invoke();

This causes the PowerShell call to happen on the caller's (MMC's) thread, and everyone is happy. 

Monday, 26 February 2007 11:09:26 (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [2]  | 
# Thursday, 22 February 2007

Update:  OK this is even wierder.  If I call into the PowerShell Runspace from inside a WinForms app marked STAThread, but first I create my own MTA thread and use it to make the call to the Runsapce, everything works.  However, from within MMC, even if I create the MTA thread myself to call the Runsapace on, it fails with the same "Interface not registered" error.

 

 

Our plan is to build PowerShell Cmdlets and drive providers to expose our management interfaces, then layer an MMC interface on top of them to provide a GUI management tool. 

Yesterday, I ran into the first hitch in that plan...

My PowerShell drive provider makes use of the AzMan COM interfaces to deal with Operations, Roles, and Tasks.  That works great from within PowerShell, and it does all the stuff it needs to.  However, when I started running the PowerShell stuff through a Runspace inside of MMC, the AzMan calls started failing with "Interface not registered".  Hmm.  So, on a hunch, I tried the same PowerShell calls (again using a Runspace) inside a WinForms app.  Same problem.  Even though they work fine in PowerShell (which obviously makes me doubt that the interface isn't registered). 

On another hunch, I changed the COM threading model attribute on the WinForms app from [STAThread] to [MTAThread].  Everything worked just peachy.  Dang. 

Turns out that MMC always calls snap-ins on an STA thread, and for the life of my I can't find any way to change that behavior.  I still don't know why the problem manifests itself quite this way.

To further qualify the problem, I called the AzMan interfaces directly from within the WinForms app, and it worked in both STA and MTA threads.  So apparently it has something to do with launching the Runspace from an STA thread, and then making the COM calls from PowerShell (which uses an MTA thread). 

I'm hoping that I won't have to abandon the MMC -> PowerShell concept for this particular part of the product, but right now I'm not sure how to resolve this.

Thursday, 22 February 2007 12:57:24 (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, 31 January 2007

If you are working with AzMan at all, the best resource I've found so far that you'll want to check out is Developing Applications Using Windows Authorization Manager from MSDN.  It includes a very comprehensive set of info on working with AzMan using Windows, ADAM, or custom principals, on using AzMan from ASP.NET, and general info on authorization strategy and policy.

There are lots of code samples included in the document, including code for doing access checks using ADAM principals, and some for writing AzMan "BizRules" in managed code.

Good stuff.

AzMan | Work
Wednesday, 31 January 2007 15:05:23 (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]  | 
# 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]  |