Programmer to ProgrammerTM | |||||
|
|
|
|
|
|
|
|
|
|
|
| |||||||||||||||||||
The ASPToday
Article April 29, 2002 |
Previous
article - April 26, 2002 |
Next
article - April 30, 2002 | |||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||
ABSTRACT |
| ||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||
Article Discussion | Rate this article | Related Links | Index Entries | ||||||||
ARTICLE |
Note: this article requires Active Directory to be running on Windows 2000 Server.
Security is an issue that reminds most system administrators that minor details about a requirement can be costly. We must consider various types and methods of security that must be implemented in order to maintain order and keep us safe. Assumptions about different parts of the system must be considered and prioritized according to the design of a project. Sections such as Security Holes (for example an OS that hasn't being patched properly or an open port in a server) and Security Service Model can be broken down into other roles for the implementation of security within a system. We will keep this article focused on the security service model.
The security service model can be a very painful requirement for a system. We must be careful when assuming the system's needs, as well as fulfilling the system's requirements. For example, business rules imply simple requirements for an organization: managers can access employee's folders; employees cannot access CEO's folders. Say that you're responsible for creating an Internet portal for controlling the access rights to a user to access a folder, customizing the way it can be accessed (read-only, read-and-write, full-access). By analyzing this case we must not overlook the way it should be implemented by organizing all entity resources to be used in it e.g. IIS, NTFS, Active Directories. When we build a house, we must build it on top of something, when we create, delete, modify, move or copy files we must perform these tasks on top of a file system, which is a key to the security for the system itself.
The Object-Oriented security model provides a distributed way to implement accurate security policies and authentication. NTFS is here to maintain a promise of effective reliability and overall distribute files across networks with a consistent level of security.
In order to understand NTFS, we must first look at the conceptual security model that wraps NTFS and service the entire platform (in this case Windows 2000). Security Account Manager (SAM) is a database for all information that validates users and groups in the NT security model, any application that tries to access the NTFS space has to be authenticated through the SAM's account. Active Directories is a management tool for Windows 2000 for dealing in controlling most of the authentication objects and has the power to Delegate responsibilities of manipulating policies to a specific group or user.
One of the many important facts about NTFS is that allows access rights to any directory or file, and once you provide the information of access right; it can be inherited from its parent's object into the file space. It makes an administrator's job much easier so they can prepare the "soil" for planting some network requirements and implementing them without the great effort of customizing each individual object (File or Directory).
Understanding a little more about Active Directories' objects we get into a very important topic which is "Authentication", but this is out of the scope of the article, we will though, poke it a little as we go. Active Directories objects such as users and groups have their own properties, methods and events. The same with access rights - we can also programmatically control these in order to fulfill a specific business requirement. Windows 2000 objects (files, folders, registry, etc) encapsulate all security objects in a Security Descriptor. The Security Descriptor has all information about objects and who can access and scope the object in question by having a list of all permission descriptors of users and groups and what they can access, called Access Control List (ACL).
There are mainly two different types of ACLs, Discretionary (user security access) and System (for self security access); we will not talk about System Security Descriptor at this stage. Discretionary Access Control List (DACL) contains all information of users and groups in an entry calledAccess Control Entry (ACE) within these entries permissions are defined for each entity.
A more practical example of ACL:
It is important to understand a little about how ACL and ACE fit together with Active Directories objects (ADSI). As we already know in Active Directories we can manage users and groups, but we can also programmatically access its properties and methods. Understanding that all Windows 2000 objects have Security Descriptors it is accurate to say that Active Directories also have Security Descriptors and these are used to control ACLs. ACLs are, in fact, container objects for ACEs (as we saw in the diagram above) for this reason everything comes together just fine in a beautiful conceptual security model.
Now that we have covered the basics we need to put some time into identifying some goals and design the interfaces for the examples we are going to produce. Firstly we need to establish the base classes that will be involved as basic requirements.
We need to get ADsSecurity.dll. You can obtain it from ADSI SDK 2.5 from http://msdownload.microsoft.com/msdownload/adsi/2.5/sdk/x86/en/Sdk.zip?WROXEMPTOKEN=348266ZGFMqNr3xDmskg3oure5
Once you've downloaded the SDK just unzip and register the dll:
C:\regsvr32 ADsSecurity.dll
In order to use resources such as ACL, ACE or ADSI via ASP, it is fair to say that we have two choices: Create ASP code instantiating all different objects and creating a very high cohesive product, or write a few components (COM) making life a lot easier.
Before going into ASP and gathering some examples we are to write a custom COM to be specific for this article's code. We are going to build a WSC (Windows Script Host) COM. I would advise anyone to use Microsoft Script Component Wizard, which can be downloaded from http://msdn.microsoft.com/scripting/scriptlets/wz10en.exe?WROXEMPTOKEN=348266ZGFMqNr3xDmskg3oure5, it just makes life easier, especially by creating all methods skeletons and generating a GUID.
I have created a class diagram with the design of our COM. It is needed to create some interfaces for the following resources:
The reason for ADSI is that we are going to create an ADSI object and access its Security Descriptor in order to manipulate the ACL and ACE of that specific object (mostly to retrieve groups and usernames). Security is the core of our assignment - it will allow us to change and add entries to the ACL. Finally, XML is a resource that I like using to substitute the comma delimited data between interfaces. You see, I normally pass comma delimited data by transposing it across a network and now with XML we can do much more than just send data across, we can actually send self descriptive data and choose so many ways to deal with it.
This class will be a wrapper for our examples consistently for other types of projects would be advisable to write individual wrappers for different resources:
Because we will not be branching into these resources, we are going to purposely wrap all these into one called ArticleUtil.WSC.
Now that we are aware of what we need to implement in our COM we'll start from the top with ADSI. At this point we just want to achieve two things with ADSI: retrieve all groups and users. For this job I will create two different methods ADSI_RetrieveAllGroups() and ADSI_RetrieveUsers(). Note the prefix I am using, ADSI_, because we are going to implement different interfaces for different resources, I like to attach a prefix to define what we are talking about.
Starting with the initialization of the COM, we need to gather some info for dealing with ADSI objects. Active Directories is exposed by Lightweight Directory Access Protocol (LDAP), not getting too much into it, LDAP is a protocol that exposes directories and their information for public use. It supports searching, retrieving objects and modifying. Across LDAP we can retrieve an LDAP path for a specific object in Active directories. By referencing this path LDAP://CN=Users,DC=myDomain,DC=au , will get a return of the Users group object, where CN is common name and DC is the distinguished name. Here is the initialization method for the COM:
Function Initialize() Dim oArray Dim oSysInfo Set oSysInfo = CreateObject("ADSystemInfo") 'Spliting UserName, to retrieving LDAP:// path. oArray = Split( oSysInfo.ForestDNSName, "." ) gsDOMAIN = oArray(0) gsPath = "CN=Users,DC=" & oArray(0) & ",DC=" & oArray(1) 'Destroy objects Set oSysInfo = Nothing Set oArray = Nothing End Function
This is the first method we call when instantiating and object from ArticleUtil.WSC COM. The whole point of this method is to get the Forest DNS name of the Domain Controller "myDomain.com " and create a valid LDAP path, storing it in a global variable for later use.
We are now ready to create the first ADSI method ADSI_RetrieveAllGroups():
Dim oNameSpace Dim sXML Dim oContainer Dim oItem Dim sList Dim sItem Set oContainer = GetObject( "LDAP://" & gsPath ) for each oItem in oContainer if oItem.Class = "group" Then sList = sList & "<GroupName" & oItem.name & "</ GroupName >" end if next sXML = "<root>" & sList & "</root>" Set oNameSpace = nothing Set oContainer = Nothing Set oItem = Nothing vADSI_RetrieveAllGroups = sXML End Function
This method is a very simple example of how to access some objects in Active Directories. The variable gsPath contains the value of the LDAP path for CN=USERS,DC=MyDomain,DC=Com , which is the Users container in ADSI with all default Users and Groups.
Calling this method will return all groups in CN=USERS , which is the Users' group in Active Directories returning then in a String formatted in XML:
<root> <GroupName>CN=Cert Publishers</GroupName> <GroupName>CN=Debugger Users</GroupName> <GroupName>CN=DnsAdmins</ GroupName> <GroupName>CN=DnsUpdateProxy</GroupName> <GroupName>CN=Domain Admins</GroupName> <GroupName>CN=Domain Computers</GroupName> <GroupName>CN=Domain Controllers</GroupName> <GroupName>CN=Domain Guests</GroupName> <GroupName>CN=Domain Users</GroupName> <GroupName>CN=Enterprise Admins</GroupName> <GroupName>CN=Group Policy Creator Owners</GroupName> <GroupName>CN=RAS and IAS Servers</GroupName> <GroupName>CN=Schema Admins</GroupName> <GroupName>CN=VS Developers</GroupName> </root>
Using XML in common interfaces used by simple applications everyday helps developers to standardize their code making it more clear and with manipulative outputs. Going further here is the method ADSI_RetrieveUsers():
Function ADSI_RetrieveUsers() Dim oNameSpace dim sXML Dim oContainer Dim oItem Dim sList Dim oDOM Dim sItem Set oContainer = GetObject( "LDAP://" & gsPath ) for each oItem in oContainer sItem = Right( oItem.name, Len( oItem.name ) - 3 ) if oItem.Class = "user" and sItem <> "ASPNET" _ and sItem <> "Guest" and sItem <> "krbtgt" _ and Left(sItem,4) <> "IUSR" _ and Left(sItem, 4) <> "IWAM" and sItem <> "SQLDebugger" _ and sItem <> "TsInternetUser" Then sList = sList & "<UserName>" & right(oItem.name,Len(oItem.name)-3) & "</UserName>" end if next sXML = "<root>" & sList & "</root>" Set oContainer = Nothing Set oItem = Nothing ADSI_RetrieveUsers = sXML End Function
Very similar to retrieving all groups from the Users group path, retrieving users is no different, we just need to consider that CN=USERS is a container for objects of class type "user " and "group ", and that we just need to verify for "user " class instead of "group ".
<root> <UserName>Administrator</UserName> <UserName>Erick Sgarbi</UserName> <UserName>John Mentor</UserName> <UserName>Natalie Bridge</UserName> </root>
As we can see, the Initialize() method builds the LDAP path by pointing to the Users container in Active Directories and saving it to a global variable that is used by ADSI_RetrieveAllGroups() and ADSI_RetrieveUsers(). There is no need to clean up any objects from Initialize() since all objects have being purposely already destroyed in the Initialize() method.
Another method implemented in the class is SEC_DumpACL(sPath) that takes a String as argument representing a path of a specific directory. This method will return a XML formatted String which contains details about all ACEs in that specific Descriptor, remember that we are talking about one descriptor that may contain many ACEs.
Function SEC_DumpACL (sPath) Dim oSD, oDacl, oAce, oSec, sResponse Set oSec = CreateObject("ADsSecurity") Set oSd = oSec.GetSecurityDescriptor( CStr(sPath) ) Set oDacl = oSd.DiscretionaryAcl For Each oAce In oDacl sResponse = sResponse & "<ACE>" & "<Trustee>" & oAce.trustee & "</Trustee>"_ & "<Type>" & oAce.AceType & "</Type>"_ & "<AccessMask>" & oAce.AccessMask & "</AccessMask>" _ & "<AceFlags>" & oAce.AceFlags & "</AceFlags>" & "</ACE>" Next sResponse = "<root>" & sResponse & "</root>" if err.number <> 0 then SEC_DumpACL = "Error number: " & err.number & ", description :" & err.description else SEC_DumpACL = sResponse End if End Function
These methods will return a String formatted in XML. In order to manipulate and take advantages of this format we must create a XML DOM object, for this we'll have another method in our COM to load a string into a DOM object:
Function XML_LoadXMLString( ByVal vsString, ByRef roDOMDOC ) Set roDOMDOC = CreateObject( "Microsoft.XMLDOM" ) Call roDOMDOC.LoadXML( vsString ) End Function
Calling this method we will pass the String XML by value and a second variable to be instantiated as a Microsoft.XMLDOM being returned by reference, this reference must be destroyed in the code that is creating the COM object.
In order to understand the DOM Document handling we can start by setting up an ASP file to call SEC_DumpACL(sPath) , passing C:\Winnt to get an XML file with details about the ACEs in the ACL:
<HTML> <TABLE id="Table1" cellSpacing="1" cellPadding="1" width="600" border="1"> <% Dim oCOM, sResponse, oDOM, oNodes Set oCOM = Server.CreateObject("ArticleUtil.WSC") s = oCOM.SEC_DumpACL("FILE://C:\winnt") call oCOM.XML_LoadXMLString(s, oDOM) Set oNodes = oDOM.selectNodes( "root/ACE") For each oNode in oNodes sResponse = sResponse & _ "<TR><TD>Trustee :" & _ oNode.selectSingleNode( "Trustee").text & "</TD>" & "<TD>Type :" & oNode.selectSingleNode( "Type").text & "</TD><TD>Access Mask :" & oNode.selectSingleNode( "AccessMask").text & "</TD><TD>Flags :" & oNode.selectSingleNode( "AceFlags").text & "</TD></TR>" Next Set oDOM = Nothing Set oCOM = Nothing response.Write sResponse %> </HTML>
Here is the output:
These are the properties from the ACEs of the C:\Winnt folder. Going through some of these properties and values, this table presents us with four properties:
Getting into the only method that actually does something, SEC_DACL_Authority(sXML) this method takes an XML string with the following Schema:
<root> <User> <UserName></UserName> <Permission></Permission> <Path></Path> </User> </root>
It has information about the User to be affected by the change (must be an existing user from current Active Directories), the type of permission and the folder to be affected. As I mentioned before, this is different way of passing arguments and getting return from methods. By using this approach it is even possible to prepare a method to receive an XML string with various <User></User> nodes applying changes for various users at the same time. But for now, here is the method:
Function SEC_DACL_Authority( sXML ) Dim oACE, oSec, oSD, oDACL, sTempDACLAccount Dim oDOM Dim sPath, sUser, sPermission on error resume next 'DO XML Call XML_LoadXMLString( sXML, oDOM) 'Strip out all XML info into local variables sPath = oDOM.selectSingleNode( "root/User/Path" ).text sUser = oDOM.selectSingleNode( "root/User/UserName" ).text sPermission = oDOM.selectSingleNode( "root/User/Permission" ).text 'Create Security Object Set oSec = CreateObject("ADsSecurity") 'Create Security Descriptor Set oSD = oSec.GetSecurityDescriptor( CStr(sPath) ) 'Create DACL Set oDACL = oSD.DiscretionaryAcl 'Create ACE Set oACE = CreateObject("AccessControlEntry") oACE.Trustee = sUser 'Localize permission type If UCase (sPermission) = "FULL" Then oACE.AccessMask = ACE_RIGHT_FULL Elseif UCase (sPermission) = "READ-EXECUTE" Then oACE.AccessMask = ACE_RIGHT_READ Or ACE_RIGHT_EXECUTE Elseif UCase (sPermission) = "MODIFY" Then oACE.AccessMask = ACE_RIGHT_READ Or ACE_RIGHT_WRITE _ Or ACE_RIGHT_EXECUTE or ACE_RIGHT_DELETE End If oACE.AceFlags = ADS_ACEFLAG_INHERIT_ACE oACE.AceType = ADS_ACETYPE_ACCESS_ALLOWED oDACL.AddAce oACE oSD.DiscretionaryAcl = oDACL oSec.SetSecurityDescriptor oSD 'Destroy Security Descriptor Set oSec = Nothing 'Destroy oACE set oACE = Nothing 'Destroy oDACL Set oDACL = Nothing 'Destroy oDOM Set oDOM = Nothing If err.number <> 0 Then SEC_DACL_Authority = err.description else SEC_DACL_Authority = true end if End Function
After instantiating ArticleUtil.WSC we can call this method by passing an XML string with all the properties populated. This XML string will get loaded into a DOMDocument:
'DO XML Call XML_LoadXMLString( sXML, oDOM)
Once we have an object we can call methods and properties in order to get values from it:
'Strip out all XML info into local variables sPath = oDOM.selectSingleNode( "root/User/Path" ).text sUser = oDOM.selectSingleNode( "root/User/UserName" ).text sPermission = oDOM.selectSingleNode( "root/User/Permission" ).text
After chopping, we are to create all security objects to start preparing entries.
'Create Security Object Set oSec = CreateObject("ADsSecurity") 'Create Security Descriptor Set oSD = oSec.GetSecurityDescriptor( CStr(sPath) ) 'Create DACL Set oDACL = oSD.DiscretionaryAcl 'Create ACE Set oACE = CreateObject("AccessControlEntry")
The directory path is passed into the Security object by its Security Descriptor. Remember that every object in Windows 2000 has a Security Descriptor then getting the Discretionary reference. The ACE is instantiated implicitly.
The ACE is the entry we are to apply into the ACL, firstly we need to populate the ACE object so it can make some sense when added into the ACL.
oACE.Trustee = sUser 'Localize permission type If UCase (sPermission) = "FULL" Then oACE.AccessMask = ACE_RIGHT_FULL Elseif UCase (sPermission) = "READ-EXECUTE" Then oACE.AccessMask = ACE_RIGHT_READ Or ACE_RIGHT_EXECUTE Elseif UCase (sPermission) = "MODIFY" Then oACE.AccessMask = ACE_RIGHT_READ Or ACE_RIGHT_WRITE _ Or ACE_RIGHT_EXECUTE or ACE_RIGHT_DELETE End If oACE.AceFlags = ADS_ACEFLAG_INHERIT_ACE oACE.AceType = ADS_ACETYPE_ACCESS_ALLOWED
The property Trustee is the user that is assigned permission rights to. ACE supports six different types of AceType , they differ as they go along with the system; three are generic types that can be exposed by any security object in Windows 2000 and the other three are Active Directories specific (not applicable to NTFS).
As most of us would expect the AccessMask is a 32-bit number that can be turned on or off depending on the AceType and what type of value it contains (allow, deny). Finally the AceFlag , which is the type of inheritance, is applied to the objects that are related to the security descriptor object. Once everything is assigned we add the ACE to the DACL and update the object's Security Descriptor.
oDACL.AddAce oACE oSD.DiscretionaryAcl = oDACL oSec.SetSecurityDescriptor oSD
As this method was intended to get an XML string with the Schema showed before with the Username, type of permission and the directory to apply the changes:
For example:
<root> <User> <UserName>Erick Sgarbi</UserName> <Permission>FULL</Permission> <Path>C:\Security Folders</Path> </User> </root>
By passing this string XML formatted packet into the method it will give the user Erick Sgarbi Full access rights to C:\Security Folders.
Sure that implementing simple security, creating interfaces and creating some test cases define some of the goals of the learning. As we know, by using ASP it is possible to create custom distributed applications with architecture robust enough to maintain entire Sites and Servers. One important aspect is the instantiation of objects in ASP. The ISR_DOMAIN which is a "real User" in Active Directories is handled by IIS in order to access and be denied system services, in fact by default the ISR_DOMAIN has privileges to create all types of objects. Remember that using ASP to instantiate COM, we are using IIS as a bridge to the Operating System impersonating a default user, and impersonation of authorities would be very welcome when implementing an ASP application.
I've put together a simple application that populates a ListBox with all users from Active Directories.
Some pre-defined Security folders were populated into the folder's ComboBox. Once the user is included, just apply the type of access right and submit the form. Another ASP page will catch these values and load them into a program just like this:
The form that is submitted looks like this:
<form id="Form1" name="thisForm" action="response.asp" method="post"> <INPUT id="Username" type="text" name="User"> <FONT size="5">Folder</FONT> <SELECT id="folderName" style="WIDTH: 296px" name="folder"> <OPTION selected>C:\Security Folders\Administration</OPTION> <OPTION>C:\Security Folders\Application data</OPTION> <OPTION>C:\Security Folders\Finance</OPTION> <OPTION>C:\Security Folders\Marketing</OPTION> </SELECT> <INPUT id="radioFull" type="radio" CHECKED value="FULL" name="permission">Full Control<INPUT id="radioModify" type="radio" value="MODIFY" name="permission"> <INPUT id="radioReadExec" type="radio" value="READ-EXECUTE" name="permission"> Read & Execute <INPUT id="Submit1" type="submit" value="Submit" name="Submit1"> </form>
The file response.asp will catch the values posted by this form and process them into the following logic:
<HTML> <TABLE id="Table1" cellSpacing="1" cellPadding="1" width="600" border="1"> <% Dim sFolder, sPermission, sUser Dim oCOM, str, sReturn, oDOM, s sFolder = request.Form("folder") sUser = request.Form("User") sPermission = request.Form("permission") Set oCOM = Server.CreateObject("ArticleUtil.WSC") Set oDOM = Nothing str = oCOM.XML_FormatACL(sUser, sPermission, sFolder) sReturn = oCOM.SEC_DACL_Authority( str ) s = oCOM.SEC_DumpACL(sFolder) call oCOM.XML_LoadXMLString(s, oDOM) Set nodes = oDOM.selectNodes( "root/ACE") For each oNode in nodes sResponse = sResponse & "<TR><TD>Trustee :" & oNode.selectSingleNode( "Trustee").text & "</TD>" _ & "<TD>Type :" & oNode.selectSingleNode( "Type").text & "</TD><TD>Access Mask :" & _ oNode.selectSingleNode( "AccessMask").text & "</TD><TD>Flags :" & _ oNode.selectSingleNode( "AceFlags").text & "</TD></TR>" Next Response.Write sResponse Set oDOM = Nothing Set oCOM = Nothing %> </html>
Prior to making any changes in the Administration folder, this is the security descriptor for it. You can access this by right clicking on the folder and going to Security
By adding the user John Mentor with full access to the Administration folder we'll get the following table as result:
This table is a formatted result from the method SEC_DumpACL(sPath) into a table. It shows that the User John Mentor has being added to the ACE.
By performing more actions in the application with different users and different permissions we are able to add more ACEs into the Descriptor:
These days, manipulating security is a very important subject for companies and organizations and it's not very difficult to start a wrapper class that can embed all logics needed for such a requirement.
A few remarks are needed to be made about applying security using ASP: security with NTFS can be very dangerous if not protected then ADSI will be the choice for authentication. Authentication is not security and security is not authentication - many developers get this wrong. With ASP, authentication can be a powerful tool if used in conjunction with NTFS.
Please rate this article using the form below. By telling us what you like and dislike about it we can tailor our content to meet your needs.
Article Information | |
---|---|
Author | Erick Sgarbi |
Chief Technical Editor | John R. Chapman |
Project Manager | Helen Cuthill |
Reviewers | Susan Connery, Sean Schade |
If you have any questions or comments about this article, please contact the technical editor.
|
| |||||||
| |||||||||||||||
|
ASPToday is brought to you by
Wrox Press (http://www.asptoday.com/OffSiteRedirect.asp?Advertiser=www.wrox.com/&WROXEMPTOKEN=348266ZGFMqNr3xDmskg3oure5).
Please see our terms
and conditions and privacy
policy. ASPToday is optimised for Microsoft Internet Explorer 5 browsers. Please report any website problems to webmaster@asptoday.com. Copyright © 2002 Wrox Press. All Rights Reserved. |