Exchange 2000 Server and Exchange Server 2003 offer the ability to set permissions not only at the folder level but also at the item and property levels for documents or other objects contained in the Exchange information store. This means that you can secure your applications even further when data is stored in a single folder. We'll discuss the security features in Exchange and look at a Web application that allows you to try out these security features.
Note | If you set item-level security in Exchange 2000 or 2003 and the item is replicated to an Exchange 5.5 server, your security will not be enforced because Exchange 5.5 does not support item-level security. |
Exchange Server supports native Windows security descriptors. Using these descriptors, you can allow or deny access to an item or the item's properties, grant this access using Windows security identifiers, and access and set permissions by viewing and modifying the descriptor in an XML format from WebDAV or ADO/OLE DB (which we discussed in Chapter 16). By providing you with an XML representation of the security descriptor, Exchange makes it easy for you to work with security settings; you do not have to learn Windows API programming to change permissions. Furthermore, the technology in Exchange takes your XML descriptor and turns it into the correct binary representation of the descriptor in the Exchange database. You can access the XML-formatted descriptor by querying for the http://schemas.microsoft.com/exchange/ security/descriptor property. The following code is an example of what is returned on an item when you query for this property from ADO:
'ADO code Dim oRecord As New ADODB.Record oRecord.Open "file://./backofficestorage/domain/apps/items/exchange.eml" strSec = oRecord.Fields( _ "http://schemas.microsoft.com/exchange/security/descriptor").value
Following are the XML results:
<S:security_descriptor xmlns:S="http://schemas.microsoft.com/security/" xmlns:D="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" D:dt="microsoft.security_descriptor"> <S:revision>1</S:revision> <S:owner S:defaulted="0"> <S:sid> <S:string_sid>S-1-5-21-1659004503-152049171-1202660629-1110</S:string_sid> <S:nt4_compatible_name>THOMRIZNT5DOM\thomriz</S:nt4_compatible_name> <S:user_principal_name>thomriz@thomriznt5dom.extest.microsoft.com </S:userprincipal_name> <S:display_name>Thomas Rizzo</S:display_name> </S:sid> </S:owner> <S:primary_group S:defaulted="0"> <S:sid> <S:string_sid>S-1-5-21-1659004503-152049171-1202660629-513</S:string_sid> <S:nt4_compatible_name>THOMRIZNT5DOM\Domain Users</S:nt4_compatible_name> </S:sid> </S:primary_group> <S:dacl S:defaulted="1" S:protected="0" S:autoinherited="1"> <S:revision>2</S:revision> <S:effective_aces> <S:access_allowed_ace S:inherited="1"> <S:access_mask>1fcfff</S:access_mask> <S:sid> <S:string_sid>S-1-5-21-1659004503-152049171-1202660629-1110 </S:string_sid> <S:nt4_compatible_name>THOMRIZNT5DOM\thomriz</S:nt4_compatible_name> <S:user_principal_name>thomriz@thomriznt5dom.extest.microsoft.com </S:user_principal_name> <S:display_name>Thomas Rizzo</S:display_name> </S:sid> </S:access_allowed_ace> <S:access_allowed_ace S:inherited="1"> <S:access_mask>1fcfff</S:access_mask> <S:sid> <S:string_sid>S-1-5-21-1659004503-152049171-1202660629-1105 </S:string_sid> <S:nt4_compatible_name>THOMRIZNT5DOM\Domain EXServers </S:nt4_compatible_name> </S:sid> </S:access_allowed_ace> <S:access_allowed_ace S:inherited="1"> <S:access_mask>1fcfff</S:access_mask> <S:sid> <S:string_sid>S-1-5-21-1659004503-152049171-1202660629-500 </S:string_sid> <S:nt4_compatible_name>THOMRIZNT5DOM\Administrator </S:nt4_compatible_name> <S:display_name>Administrator</S:display_name> </S:sid> </S:access_allowed_ace> <S:access_allowed_ace S:inherited="1"> <S:access_mask>1fcfff</S:access_mask> <S:sid> <S:string_sid>S-1-5-21-1659004503-152049171-1202660629-519 </S:string_sid> <S:nt4_compatible_name>THOMRIZNT5DOM\Enterprise Admins </S:nt4_compatible_name> </S:sid> </S:access_allowed_ace> <S:access_allowed_ace S:inherited="1"> <S:access_mask>1fcfff</S:access_mask> <S:sid> <S:string_sid>S-1-5-21-1659004503-152049171-1202660629-512 </S:string_sid> <S:nt4_compatible_name>THOMRIZNT5DOM\Domain Admins </S:nt4_compatible_name> </S:sid> </S:access_allowed_ace> <S:access_allowed_ace S:inherited="1"> <S:access_mask>12088f</S:access_mask> <S:sid> <S:string_sid>S-1-1-0</S:string_sid> <S:nt4_compatible_name>\Everyone</S:nt4_compatible_name> </S:sid> </S:access_allowed_ace> </S:effective_aces> </S:dacl> </S:security_descriptor>
This XML code is for a nonfolder security descriptor ” specifically , an item. A folder security descriptor is a bit different, but as you can see in the XML structure shown in the code, a security descriptor is made up of a Discretionary Access Control List (DACL), which in turn is made up of Access Control Entries (ACEs). Each ACE in the DACL either grants or denies a trustee a certain set of rights to the object. The access mask, which you see defined in the XML, describes the set of rights that are granted or denied to a user . Access masks are 32-bit numbers in which the upper 16 bits describe generic rights and the lower 16 bits describe object-specific rights.
Some quick points about what you see in the XML that is returned. The defaulted flag tells whether the DACL was a default DACL. For example, on item creation, a default DACL was used rather than someone specifying it. This affects how inherited ACEs are treated in the DACL. The protected flag specifies whether the DACL of the security descriptor will inherit any aces from its parent. It gets changed by an application program or when you deselect the Allow Inheritable Permissions From The Parent To Propagate check box in the Security Settings dialog box. If you want to set up custom security on your folder and not inherit from your parent, you set the protected flag to 1 for your DACL. This causes all inherited ACEs to be ignored. The autoinherited flag specifies whether the DACL can have ACEs automatically propagated to child objects. The primary group tag is for POSIX compatibility.
We need to discuss three other blocks: effective_aces , subcontainer_inheritable , and subitem_inheritable . The descriptor section for effective_aces applies to the item in question, and subcontainer_inheritable applies to subfolders and is present only in the security descriptor for folders. The subitems_inheritable section applies to items and messages and is present only in the security descriptor of a folder. When a folder or item is created, by default it gets the permissions that appear in the subcontainer/subitem block of the security descriptor of its parent folder. If the no_propagate_inherit attribute is set in the ACE, it applies only to the immediate sublevel and will not appear in the subcontainer_inheritable block of the new folder.
A quick comment about working with security in Exchange. How you create the format for your security descriptor will depend on whether you are working in a MAPI public folder top-level hierarchy (TLH) or a non-MAPI TLH. A MAPI TLH requires that you create your security descriptor using the MAPI canonical format. This is what clients , such as Outlook, expect to see; the importance of this requirement cannot be overemphasized. If you create your security descriptor using the Windows canonical format, you will break Outlook! It is also possible to lock yourself out from being able to modify existing items or folders. Therefore, you must be very careful when you work with security descriptors. I highly recommend that you just take advantage of the Security Module that ships with the Exchange SDK. It understands how to correctly create both MAPI and Windows canonical formats and includes code to make sure you don't do any harm when working with security descriptors. You can find more information on the Security Module later in this chapter.
Note | You might be wondering whether you should ever go through the M: drive to set security for your applications. In Exchange 2003, the M: drive is turned off by default, and you should try to keep it that way. However, if you turn the M: drive on, there are some security issues you should know about. For example, if you are working in non-MAPI folder trees ”which are essentially not in the MAPI-based public folder hierarchy ”you can use this method to set your security. If you are in the MAPI TLH and set security using Windows Explorer through the M: drive, you will break MAPI client applications such as Outlook and affect their ability to view and set permissions. This is because Windows Explorer formats the ACLs as Windows canonical and not MAPI canonical. In other words, do not set permissions using the M: drive for MAPI folders. |
Tables 17-8, 17-9, and 17-10 describe the kinds of access rights you can have: standard access rights, access rights on folders, and access rights on nonfolders (items), respectively. You can combine values in each table and put them into the access mask to create the correct security descriptor for the user.
If you were to create an access mask with all the properties in Table 17-8, the value would be 0x1F0000 .
Access Right | Value (Hex) | Description |
---|---|---|
fsdrightDelete | 0x00010000 | Delete |
fsdrightReadControl | 0x00020000 | Read access to security descriptor and owner |
fsdrightWriteSD | 0x00040000 | Write DACL permissions |
fsdrightWriteOwner | 0x00080000 | Used to assign write owner |
fsdrightSynchronize | 0x00100000 | Used to synchronize access to the object |
If a user were to have all rights in Table 17-9, the value of the mask would be 0xCFFF .
Access Right | Value (Hex) | Description |
---|---|---|
fsdrightListContents | 0x00000001 | Right to list contents of the directory. |
fsdrightCreateItem | 0x00000002 | Right to add a file to the folder. |
fsdrightCreateContainer | 0x00000004 | Right to add a subfolder. |
fsdrightReadProperty | 0x00000008 | Right to read extended attributes. |
fsdrightWriteProperty | 0x00000010 | Right to write extended attributes. |
fsdrightReadAttributes | 0x00000080 | Right to read file attributes. Currently unused. |
fsdrightWriteAttributes | 0x00000100 | Right to change file attributes. Currently unused. |
fsdrightWriteOwnProperty | 0x00000200 | Right to modify own items (Exchange-specific property). |
fsdrightDeleteOwnItem | 0x00000400 | Right to delete own items. |
fsdrightViewItem | 0x00000800 | Right to view items (Exchange-specific property). |
fsdrightOwner | 0x00004000 | Owner of the folder. Provided for backward compatibility. |
fsdrightContact | 0x00008000 | Contact for the folder. Provided for backward compatibility. |
If a user were to have all the rights in Table 17-10, the value would be 0x0FBF .
Access Right | Value (Hex) | Description |
---|---|---|
fsdrightReadBody | 0x00000001 | Right to read data from a file. |
fsdrightWriteBody | 0x00000002 | Right to write data to a file. |
fsdrightAppendMessage | 0x00000004 | Same as fsdrightWriteBody . Not currently used. |
fsdrightReadProperty | 0x00000008 | Right to read extended attributes. |
fsdrightWriteProperty | 0x00000010 | Right to write extended attributes. |
fsdrightExecute | 0x00000020 | Right to execute a file. Currently not used. |
fsdrightReadAttributes | 0x00000080 | Right to read file attributes. |
fsdrightWriteAttributes | 0x00000100 | Right to change file attributes. |
fsdrightWriteOwnProperty | 0x00000200 | Right to modify own item (Exchange-specific property). |
fsdrightDeleteOwnItem | 0x00000400 | Right to delete own item (Exchange-specific property). |
fsdrightViewItem | 0x00000800 | Right to view item (Exchange-specific property). |
In the preceding XML code example, you can have an access_allowed_ace , which contains the access mask for the rights you will allow for the user on the item or folder. You can also have an access_denied_ace , which specifies an access mask that contains the rights you will deny for the user on the folder or item. If the user has all rights, as in the preceding XML example, the access mask will be 0x1FCFFF , which means all rights from the three previous tables are granted to the user. Creating the access mask is just a matter of adding together the hexadecimal values from the tables and creating the ACE for the user. We'll see an example on how to create an ACE later in this chapter using the XMLDOM.
Before we drill down into a sample application, you need to know that there are certain rights the user needs in order to access the security descriptor: fsdrightReadControl , fsdrightWriteSD , and fsdrightWriteOwner .
The security sample application included with the book's companion content makes using the XML security descriptor much easier. It is a Web application that leverages XMLDOM, XSL, and WebDAV to show you how to work with and set the XML security descriptors in Exchange. Figure 17-18 shows the main interface for the security application.
One task to try when you work with the security application is restricting who can view items in an Exchange folder. The next bit of code shows how to do this. As you can see in Figure 17-19, the user can view the document named Training Materials. However, after applying the setting that denies this user access to the document, as shown in Figure 17-20, the user can no longer see the specific document but can see all the other documents contained in the folder. The figure shows what the user sees after you apply the security change.
The application works by combining XMLDOM, XSL, and WebDAV to get, display, and set item-level and property-level security in Exchange. As you can see from the following code snippet, when you click the OK button to retrieve the security for an object in Exchange, the application does a PROPFIND on the http://schemas.microsoft.com/exchange/security/descriptor property. The application then uses XSL to display the HTML that you see in the browser.
function cmdGetSec.onclick() { var strReqBody = ""; if(txtUrl.value == "") { alert("Please Enter or select a valid URL."); return; } strReqBody = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<propfind xmlns=\"DAV:\">" + "<prop xmlns:r=\"http://schemas.microsoft.com/exchange/security/\">" + " <r:descriptor/>" + "</prop>" + "</propfind>"; DAVRequest.open("PROPFIND", txtUrl.value, false); DAVRequest.setRequestHeader("Content-Type", "text/xml"); DAVRequest.setRequestHeader("Depth", "0"); DAVRequest.setRequestHeader("Translate", "f"); DAVRequest.send(strReqBody); if(chkMultiStatusForErr(DAVRequest) >= 0) { xmlResponse = DAVRequest.responseXml; perm_entries_dest.innerHTML = xmlResponse.transformNode(xslPerm_Entries); ace_entries_dest.innerHTML = "<div/>"; ace_edit_dest.innerHTML = "<div/>"; add_dest.innerHTML = "<DIV/>"; xmlResponse.transformNodeToObject(xslAceEntries.documentElement, xmlAceEntries); HiLitePermsTable(); propfindForResources(); cmdAdd.disabled = false; cmdRemove.disabled = true; cmdAceApply.disabled = true; cmdAceCancel.disabled = true; strOwner = owner.innerText; } }
To add a new ACE, all the application does is take your entered data and turn it into a new XML node that it adds to the XML security descriptor. To do this, the application fills in the required properties to correctly generate a new ACE, such as a Windows-compatible name , the access mask, as well as the ACE type. The code that does the XML work is shown here:
function addAce(strUserName, strMask, strPropName, strApplyTo, _ strRoleProp, strRoleScope, fbAllow) { // Basic variable declarations. var baseNode = null; var nodeFirstAce = null; var nodeSubNode = null; var nodeNewNode = null; var nOrder = 0; nOrder = getAceOrder(strUserName, null, strPropName, strApplyTo, strRoleProp, strRoleScope, fbAllow); while(nOrder > -1) { removeAce(nOrder); nOrder = getAceOrder(strUserName, strMask, strPropName, strApplyTo, strRoleProp, strRoleScope, fbAllow); } // Establish the base node. baseNode = xmlAceEntries.documentElement; // Create the ace node. nodeNewNode = xmlAceEntries.createNode(1, "ace", ""); // Add the NT4_Compatible_Name sub node. nodeSubNode = xmlAceEntries.createNode(1, "NT4_Compatible_Name", ""); nodeSubNode.text = strUserName; nodeNewNode.appendChild(nodeSubNode); // Add the Access_Mask sub node. nodeSubNode = xmlAceEntries.createNode(1, "Access_Mask", ""); nodeSubNode.text = strMask; nodeNewNode.appendChild(nodeSubNode); // Add the Ace_Type sub node. nodeSubNode = xmlAceEntries.createNode(1, "Ace_Type", ""); nodeSubNode.text = getAceType(strPropName, fbAllow); nodeNewNode.appendChild(nodeSubNode); // Add the Apply_To sub node. nodeSubNode = xmlAceEntries.createNode(1, "Apply_To", ""); nodeSubNode.text = strApplyTo; nodeNewNode.appendChild(nodeSubNode); // Add the Property_Name sub node. nodeSubNode = xmlAceEntries.createNode(1, "Property_Name", ""); nodeSubNode.text = strPropName; nodeNewNode.appendChild(nodeSubNode); if(strRoleScope != null && strRoleScope != "") { nodeSubNode = xmlAceEntries.createNode(1, "Role_Scope", ""); nodeSubNode.text = strRoleScope; nodeNewNode.appendChild(nodeSubNode); } if(strRoleProp != null && strRoleProp != "") { nodeSubNode = xmlAceEntries.createNode(1, "Role_Property", ""); nodeSubNode.text = strRoleProp; nodeNewNode.appendChild(nodeSubNode); } baseNode = xmlAceEntries.documentElement; baseNode.appendChild(nodeNewNode); refreshAceList(); }
To set the security, the application does a PROPPATCH to the same property but with the new security descriptor formatted as XML and the new security added as XML nodes:
function cmdAceApply.onclick() { var xmldomAce = new ActiveXObject("Microsoft.XMLDOM"); var xmlAccessMaskNode = null; var xmlAceTypeNodes = null; var xmlAceTypeNode = null; var xmlUserNameNode = null; var xmlApplyToNode = null; var xmlPropNode = null; cmdAceApply.disabled = true; cmdAceCancel.disabled = true; addAcesOutstandingAcesToXML(); xmlAceEntries.transformNodeToObject(xslProppatch.documentElement, xmldomProppatch); DAVRequest.open("PROPPATCH", txtUrl.value, false); DAVRequest.setRequestHeader("Content-Type", "text/xml"); DAVRequest.setRequestHeader("Translate", "f"); DAVRequest.send(xmldomProppatch); if(chkMultiStatusForErr(DAVRequest) >= 0) { HiLitePermsTable(); if(nCurrentAce > -1) { xmldomAce.loadXML( xmlAceEntries.childNodes(0).childNodes(nCurrentAce).xml); ace_entries_dest.innerHTML = xmldomAce.transformNode(xslDips_ACE_Info); ace_edit_dest.innerHTML = xmldomAce.transformNode(xslACE_Edit.documentElement); perm_entries_dest.innerHTML = xmldomProppatch.transformNode(xslPerm_Entries); HiLiteRow(thetable, nCurrentAce+1); } } cmdGetSec.onclick(); return ""; }