.Net and Active Directory - An OO solution to authority structure - Part 9

The practical applications for such a structure are aimed mainly at organizations robust enough to employ Active Directory that require this sort of tailored access by law or by their own culture.  Some laws in place that would necessitate such control and auditing capability include healthcare companies complying with HIPAA or any of your big corporations unfortunate enough to be governed under Sarbanes-Oxley. 

The extensibility of the solution means that it offers much more than your 'run of the mill' page level, role based permissions governance.  It is conceivable that any resource that can be ID'd and whose access to can be intercepted and checked for compliance could fall under the purview of such a system.  To place it in technical terms I see the types of resources being governed by this object including:

  • Any type of Data Source be it a DB down to stored procedures by wiring into the DAL or BLL, even defining the slice of data allowed.  A 'Data Source Owner' could delegate slices of data access to their resource by choosing Users allowed to access it and something akin to a WHERE clause injected into the data request. This could divvy up pieces of Key Performance Indicators for instance allowing a general KPI to be extended and personalized.
  • Web Pages of course, but also the smaller components that make up the modern RIA such as User Controls, Web Parts and the like.  As long as you checked for permissions any point prior to PreRender, server output could be refined or stopped completely, and that could be for different parts of one page.  To use the KPI example, numerous graphs and tools could appear on a page, owned by a department or branch head, and be accessible to their underlings, but when they accessed his dashboard, they would have access only to the tools/reports the Owner granted them access to.
  • In line with data sources would be access to accounts, patients, or any other client entity or specific data associated with an entity.  The ability to assign these entities to an individual is not what is uniquely useful to this structure but the self directed, self managed nature of delegating access that can be changed on the fly and by the user is.  For Healthcare professionals, a physician could be considered the Owner of a patient's data.  He could then delegate certain pieces of patient data or the ability to change patient data to individual users or groups, possibly granting write access for Demographics to the CheckIn Nurse Group.

Finally, if you've gotten this far, I would appreciate any comments you might have.  
You can download the source files for this series on my downloads page.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by: AaronZalewski
Posted on: 1/25/2008 at 5:30 PM
Tags: , ,
Categories: Active Directory
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

.Net and Active Directory - An OO solution to authority structure - Part 8

Well, it has been a long journey but we are almost complete.  As I mentioned at the end of Part 7, this project has a purpose. The purpose is to grant ownership of a resource that the Owner can then delegate further access to others, be that Read/Write or otherwise.  In order to take the tree structure we've created and do something useful with it, we need to have a way to extend it to include those properties & permissions the business deems necessary, store that combination of Authority structure and permissions and as all good structures must, provide a way to maintain itself should the environment change. 

The bridge to realizing this all is going to be XML, so lets look at the code to perform a custom serialization of the structure:

public XmlDocument Serialize
{
    get
    {
        if(xmldoc == null)
        {
            xmldoc = new XmlDocument();
    
            // RootNode
            XmlNode ownerNode = xmldoc.CreateNode(XmlNodeType.Element,"Owner", null);
 
            // Attributes 
            XmlAttribute att = xmldoc.CreateAttribute("Path");
            att.Value = this.Path;
            ownerNode.Attributes.Append(att);
 
            att = xmldoc.CreateAttribute("CommonName");
            att.Value = this.CommonName;
            ownerNode.Attributes.Append(att);
 
            att = xmldoc.CreateAttribute("ObjectType");
            att.Value = this.ObjectType;
            ownerNode.Attributes.Append(att);
 
            att = xmldoc.CreateAttribute("ID");
            att.Value = this.ID;
            ownerNode.Attributes.Append(att);
 
            att = xmldoc.CreateAttribute("Login");
            att.Value = this.Login;
            ownerNode.Attributes.Append(att);
 
            xmldoc.AppendChild(ownerNode);
 
            XmlNode PermissionsNode = xmldoc.CreateNode(XmlNodeType.Element, "Permissions", null);
            ownerNode.AppendChild(PermissionsNode);
            XmlNode DirectReportsNode = xmldoc.CreateNode(XmlNodeType.Element,"DirectReports",null);
            PermissionsNode.AppendChild(DirectReportsNode);
            XmlNode GroupsNode = xmldoc.CreateNode(XmlNodeType.Element,"Groups",null);
            PermissionsNode.AppendChild(GroupsNode);
            XmlNode MembersNode = xmldoc.CreateNode(XmlNodeType.Element,"Members",null);
            PermissionsNode.AppendChild(MembersNode);
            XmlNode ManagerNode = xmldoc.CreateNode(XmlNodeType.Element,"Manager",null);
            PermissionsNode.AppendChild(ManagerNode);
 
            XmlNode PeerNode = xmldoc.CreateNode(XmlNodeType.Element,"Peer",null);
            XmlNode MngrNode = xmldoc.CreateNode(XmlNodeType.Element,"Manager",null);
            XmlNode ReportsNode = xmldoc.CreateNode(XmlNodeType.Element,"Reports",null);
 
            if(this.DirectReports.Count > 0)
            {
                GetUserNodes(DirectReports,DirectReportsNode);
            }
 
            if(this.Groups.Count > 0)
            {
                GetGroupNodes(Groups,GroupsNode);
            }
 
            if(this.Members.Count > 0)
            {
                GetUserNodes(Members,MembersNode);
            }
 
            if(this.Manager != null)
            {
                ManagerNode.AppendChild(GetManagerNode(this.Manager));
            }
        }
        return xmldoc;
    }
}
 
private void GetUserNodes(UserCollection uc, XmlNode parentNode)
{
    XmlNode xNode;
    XmlAttribute att;
    foreach(User user in uc)
    {
        xNode = xmldoc.CreateNode(XmlNodeType.Element,"User",null);
        att = xmldoc.CreateAttribute("Path");
        att.Value = user.Path;
        xNode.Attributes.Append(att);
 
        att = xmldoc.CreateAttribute("UserName");
        att.Value = user.UserName;
        xNode.Attributes.Append(att);
 
        att = xmldoc.CreateAttribute("ID");
        att.Value = user.ID;
        xNode.Attributes.Append(att);
 
        att = xmldoc.CreateAttribute("Login");
        att.Value = user.Login;
        xNode.Attributes.Append(att);
 
        if(user.Groups.Count > 0)
        {
            XmlNode GroupsNode = xmldoc.CreateNode(XmlNodeType.Element,"Groups",null);
            GetGroupNodes(user.Groups,GroupsNode);
            xNode.AppendChild(GroupsNode);
        }
 
        if(user.DirectReports.Count > 0)
        {
            XmlNode DRsNode = xmldoc.CreateNode(XmlNodeType.Element,"DirectReports",null);
            GetUserNodes(user.DirectReports,DRsNode);
            xNode.AppendChild(DRsNode);
        }
                    
        parentNode.AppendChild(xNode);
    }
}
 
private void GetGroupNodes(GroupCollection gc, XmlNode parentNode)
{
    XmlNode xNode;
    XmlAttribute att;
    foreach(Group group in gc)
    {
        xNode = xmldoc.CreateNode(XmlNodeType.Element,"Group",null);
        att = xmldoc.CreateAttribute("Path");
        att.Value = group.Path;
        xNode.Attributes.Append(att);
 
        att = xmldoc.CreateAttribute("GroupName");
        att.Value = group.GroupName;
        xNode.Attributes.Append(att);
 
        att = xmldoc.CreateAttribute("ID");
        att.Value = group.ID;
        xNode.Attributes.Append(att);
 
        if(group.Users.Count > 0)
        {
            XmlNode UsersNode = xmldoc.CreateNode(XmlNodeType.Element,"Users",null);
            GetUserNodes(group.Users,UsersNode);
            xNode.AppendChild(UsersNode);
        }
                    
        parentNode.AppendChild(xNode);
    }
}
 
private XmlNode GetManagerNode(ManagerObject mgrObj)
{
    XmlAttribute att;
    XmlNode xNode = xmldoc.CreateNode(XmlNodeType.Element,"Manager",null);
 
    att = xmldoc.CreateAttribute("Path");
    att.Value = mgrObj.Path;
    xNode.Attributes.Append(att);
 
    att = xmldoc.CreateAttribute("ManagerName");
    att.Value = mgrObj.ManagerName;
    xNode.Attributes.Append(att);
 
    att = xmldoc.CreateAttribute("ID");
    att.Value = mgrObj.ID;
    xNode.Attributes.Append(att);
 
    att = xmldoc.CreateAttribute("Login");
    att.Value = mgrObj.Login;
    xNode.Attributes.Append(att);
 
    if(mgrObj.Manager != null)
    {
        xNode.AppendChild(GetManagerNode(mgrObj.Manager));
    }
 
    return xNode;
}

I hope you are familiar with producing XML via .Net because I am not going to go into that sort of detail here.  What I do want to point out is that we are performing a pretty standard serialization of the object.  This block is to be found at the bottom of the Owner class and is meant to be extended upon by you.  Rather than predict every application that might leverage such a structure, this is meant to be customized.  If you needed to have a Read and Write bit associated with each User, then the above serialization is where to provision for this.  Want to define whether the Owner can grant permissions to their Peers, add an attribute.  This is the bit where we take what is an academic exercises and turn it into a valuable tool for an organization.

Once you've extended the XML schema, passed in a real user to the GetAuthorityTree method in the Authority class, produced this XML, populated the extended bits you've added to represent business rules and permissions, now you have an XmlDocument that can be stored in the database and used to grant or deny whatever it it you are using this for.

Finally because Active Directory is not a static structure and people do get hired and fired, producing this XML and comparing it against the stored copy will tell you whether someone was added, removed or moved within the organization.  If the node structure doesn't compare properly, you will know to regenerate the structure, moving over the current settings and re-saving.  This way someone who was given access to a resource by their manager, but is moved to a different department no longer under the Owner's authority does not retain their permission to this resource.  Every good structure should contain a method for self-maintenance.  It also allows for the permissions associated with the current resource to be copied to a different resource, automatically replicating permissions and reducing the cost of ownership.

I sincerely hope you found this series helpful.  It represents many hours of work and the loss of more than a few handfuls of hair in the process.  The next and final post contain my conclusions following the completion of this structure and how it might be used within an organization.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by: AaronZalewski
Posted on: 1/23/2008 at 1:08 PM
Tags: , ,
Categories: Active Directory
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

.Net and Active Directory - An OO solution to authority structure - Part 7

Ok, now that we have walked from our Owner down the organizational tree, lets climb up and create a lineage of Manager objects.

The Manager object is a bit different.  Since our Owner could be a Group, our Manager could also.  Also at some point there is no higher to go on the tree and we find ourself looking at a Manager object who is invariably playing golf. So we have to build flexibility into this object and a way to dead end the recursion.  So lets just look at the Manager object in it's entirety:

using System;
using System.DirectoryServices;
 
namespace ActiveDirectory_Demo1.GlobalObjects.AuthorityObjects
{
    /// <summary>
    /// Summary description for ManagerObject.
    /// </summary>
    public class ManagerObject : BaseObject
    {
        private string managername;
        private string id;
        private string login;
        private string homepage;
        private ManagerObject manager;
        private DirectoryEntry originaldirectoryentry;
        private string objecttype;
        private string path;
 
        /// <summary>
        /// The Manager Object is a user or group which represents the entity that manages the object below it.
        /// </summary>
        /// <param name="de">DirectoryEntry object (user/group) to derrive the Manager from</param>
        public ManagerObject(DirectoryEntry de)
        {
            if(de.SchemaClassName.ToLower() == "group" && de.Properties["managedBy"].Value != null)
            {
                originaldirectoryentry = new DirectoryEntry(DEFAULTDOMAIN +"/"+ de.Properties["managedBy"].Value.ToString(), DEFAULTUSERNAME, DEFAULTPASSWORD, AuthenticationTypes.Secure);
                Path = originaldirectoryentry.Path;
            }
            else if(de.SchemaClassName.ToLower() == "user" && de.Properties["manager"].Value != null)
            {
                originaldirectoryentry = new DirectoryEntry(DEFAULTDOMAIN +"/"+ de.Properties["manager"].Value.ToString(), DEFAULTUSERNAME, DEFAULTPASSWORD, AuthenticationTypes.Secure);
                Path = originaldirectoryentry.Path;
                HomePage = GetHomePage();
            }
            else
            {
                ObjectType = null;
                ManagerName = null;
                HomePage = null;
            }
 
            if(originaldirectoryentry != null)
            {
                ID = de.NativeGuid;
                Login = originaldirectoryentry.Properties["sAMAccountName"].Value.ToString();
                ObjectType = originaldirectoryentry.SchemaClassName.ToLower();
                ManagerName = this.originaldirectoryentry.Name.Replace("CN=","");
                Manager = GetManager(this.originaldirectoryentry);
            }
        }
 
        /// <summary>
        /// Name of the user.
        /// </summary>
        public string ManagerName
        {
            get {  return managername; }
            set { managername = value; }    
        }
 
        /// <summary>
        /// NativeGuid of the DirectoryEntry
        /// </summary>
        public string ID
        {
            get { return id; }
            set { id = value; }
        }
 
        /// <summary>
        /// Login used to authenticate
        /// </summary>
        public string Login
        {
            get{ return login; }
            set{ login = value; }
        }
 
        /// <summary>
        /// HomePage
        /// </summary>
        public string HomePage
        {
            get { return homepage; }
            set { homepage = value;}
        }
 
        /// <summary>
        /// Manager Object who is the manager of this Object
        /// </summary>
        public ManagerObject Manager
        {
            get {  return manager; }
            set { manager = value; }  
        }
 
        /// <summary>
        /// Type of object (user/group)
        /// </summary>
        public string ObjectType
        {
            get { return objecttype; }
            set { objecttype = value; }
        }
 
        /// <summary>
        /// Path from the oringinal DirectoryEntry
        /// </summary>
        public string Path
        {
            get{ return path; }
            set{ path = value; }
        }
 
        private ManagerObject GetManager(DirectoryEntry de)
        {
            Object mngrObj = null;
 
            if(de.SchemaClassName.ToLower() == "group")
            {mngrObj = de.Properties["managedBy"].Value;}
            else if(de.SchemaClassName.ToLower() == "user")
            {mngrObj = de.Properties["manager"].Value;}
            
            if(mngrObj != null)
            {
                return new ManagerObject(de);
            }
            else
            {return null;}
        }
 
        private string GetHomePage()
        {
            string rtn = "";
            if(this.originaldirectoryentry.Properties["wWWHomePage"].Value != null)
            {rtn = this.originaldirectoryentry.Properties["wWWHomePage"].Value.ToString();}
            return rtn;
        }
    }
}

And add a Manager to our Owner and call into this class to populate it:

private ManagerObject manager;
 
/// <summary>
/// User or Group which manages this object
/// </summary>
public ManagerObject Manager
{
    get 
    { return manager;    }
    set 
    { manager = value; }  
}
public Owner(DirectoryEntry de)
{
    .
    .
    .
    if(HasManager(de))
    {Manager = new ManagerObject(de);}
    .
    .
    .
}

*Note that the HasManager method is in the BaseObject our Owner inherits.

Rather than starting at the constructor lets take a quick peek at the GetManager method.  Here we see how the DirectoryEntry passed into this method could be a "user" or "group".  And depending on which type we find, we instantiate the correct ManagerObject using the appropriate DirectoryEntry.  Here also we see that if we find neither a User nor Group above our current Manager, we return null and don't try and climb any higher.

Looking at the constructor we likewise see handling for both a Group or User. We also see that if we find insufficient information to construct a DirectoryEntry, our last IF statement terminates if the originaldirectoryentry (not the DirectoryEntry passed in) is NULL.  In all likelihood when this condition is met we are looking at the President or CEO (depending on how your organization is constructed) and walking any higher would simply place us at the Root Node for Active Directory.

Whew!  What a journey, but if you've been following the solution provided you should now have a firm grasp on interacting with Active Directory through .Net to produce a chain of authority.  As stated in the Intro post, this is a project with a purpose.  Knowing who has authority over whom is academic, doing something useful with it should be the ultimate goal (unless you just like reading about this stuff in your spare time without any practical application for it which in that case you should go outside and get some fresh air).

Anyhoo, in the next post we will create a custom serialization method for the Owner object, the purpose of which will be storage, extending our object with permissions related information and to compare it upon regeneration.  Why is that important?  Read on....

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by: AaronZalewski
Posted on: 1/23/2008 at 12:07 PM
Tags: , ,
Categories: Active Directory
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

.Net and Active Directory - An OO solution to authority structure - Part 6

So, if we envision what we have represented so far, our Owner can "see" all those people in his immediate sphere of influence.  But if your boss's boss told you to do something, would you do it?  Well, maybe you wouldn't, but you should because ultimately he has authority over you.  That is what we will do in this post, expand our owner's vision so he can see not just the people and groups that directly report to him, but those that report to them, and so on and so forth.

Lets start with expanding the User object to capture, like our Owner, the Users and Groups that directly report to them.

private UserCollection directreports;
private GroupCollection groups;
 
/// <summary>