We have just seen several examples showing how the Evidence class can be used in a direct manner to make CAS decisions within your code according to the discovery of host and assembly evidence. The other way of approaching CAS is to let the code access permission classes automatically detect any mismatch between the current security policy and the permissions required by the running code. Then, if your program attempts something that it is not permitted to perform, a SecurityException is automatically thrown. CodeAccessPermission Derived ClassesCAS programming usually involves using the classes derived from CodeAccessPermission , which are shown in the following list. Since these classes are not all contained within the same namespace, their fully qualified names are provided here for clarity. Most of the CodeAccessPermission -derived classes have meanings that are made obvious by their names. For example, DBDataPermission controls access to a database, PrintingPermission controls access to printers, SocketPermission represents the permission for making or accepting TCP/IP connections, and so on. A subset of these code access permission classes are known as identity permissions, since they do not deal with controlling access to resources but rather specialize in dealing with host evidence pertaining to assembly identity. You can easily recognize these classes, since their names contain the word Identity, such as SiteIdentityPermission and ZoneIdentityPermission . Note that this list of CodeAccessPermission -derived classes does not contain the PrincipalPermission class discussed in the previous chapter. This is because PrincipalPermission is a peer class derived from Object , and it is not a code access permission but a permission that encapsulates user -based security.
The CodeAccessPermission ClassThe CodeAccessPermission class has several methods that must be understood , since they are found in all of the derived permission classes that you will be working with. We look at a few of these methods in more detail and see example code demonstrating how they can be used. But first, let's take a look at some brief descriptions of these methods. The methods that are simply inherited from the Object class are not shown here.
Notice that certain methods, such as Copy and IsSubsetOf are abstract. You do not normally call these abstract methods in application code. Instead, these methods are called by .NET Framework CAS- related code. If you implement your own custom permission class, you will need to implement these abstract methods so that it can work properly with .NET Framework CAS functionality. To give you an idea of how a couple of these methods work under the covers, the following source code is found in the codeaccesspermission.cs file, provided by the Rotor BCL Documentation. [24]
[View full width]
The UrlIdentityPermission ClassIn this section we focus on just one of the CodeAccessPermission -derived classes, UrlIdentityPermission . This class encapsulates a permission based on the URL from which the assembly originates. We also study the UrlIdentityPermission example to see exactly how this class can be used, but we must first understand a bit about the UrlIdentityPermission class itself. The UrlIdentityPermission class provides implementations for each of the abstract methods of CodeAccessPermission described earlier. It also overrides a few nonabstract CodeAccessPermission methods. It is sufficient to review the descriptions provided in the previous section to understand the purpose of each method. Beyond that, the UrlIdentityPermission does not add much in terms of new functionality. The only new members that have been added are two constructors and one string property named Url . THE URLIDENTITYPERMISSION CONSTRUCTORSOne of the UrlIdentityPermission constructors initializes the new permission object with a PermissionState parameter. PermissionState defines two values: None and Unrestricted . However, the UrlIdentityPermission constructor does not allow the Unrestricted value, and you must therefore specify None to avoid throwing an ArgumentException . public UrlIdentityPermission( PermissionState state //None is the only valid permission state ); The other constructor initializes a new permission object based on a string parameter that represents a specific URL. The string may contain an optional wild card in the final position. However, the string must not be null and must contain a valid URL syntax, or an ArgumentNullException, FormatException , or ArgumentException will be thrown. public UrlIdentityPermission( string site //URL that may contain a wildcard ); THE URLIDENTITYPERMISSION URL PROPERTYThe Url read/write property is a string that includes the protocol, such as http or ftp, followed by a colon and two forward slashes, followed by a path and filename separated with single forward slashes . URL matching may be exact or it may make use of a wildcard at the rightmost position. Here are a few examples of valid URL strings. http://www.SomeWebSite.com/SomePath/TrustedClient.exe http://www.SomeWebSite.com/SomePath/* file://C:/SomePath/TrustedClient.exe Working with Code Access PermissionsWe have already pointed out that, just as was the case with user-based security, there are also two slightly different styles that can be used in the imperative approach. As we saw in the previous ImperativeCAS example, you can make security decisions explicitly by choosing the execution path using an if statement based on current application domain evidence. The decision was made between two execution branches, where one is successful and the other throws a SecurityException . This technique may be quite familiar to many traditional programmers, but the additional code required makes it slightly cumbersome. In the new-style imperative approach, you create a CodeAccessPermission -derived object representing the code access permission that you wish to discriminate on, and then you call on that permission object's Demand method. Then, any methods that you call will automatically throw a security exception if the specified permission is not honored. In other words, within the remainder of the current stackframe as well as any called method stackframes, the SecurityException will be automatically thrown where appropriate. The advantage of doing it this way is that the code is a little more simple and clean-looking, since there is no visible evidence of inspecting loop, if statement, or exception-throwing code. Just like the previous ImperativeCAS example, this UrlIdentityPermission example is an imperative rather than a declarative approach to CAS. The UrlIdentityPermission directory contains the UrlIdentityPermissionComponent project along with the EvilClient and TrustedClient projects that use the ImperativeCASComponent assembly. THE URLIDENTITYPERMISSION EXAMPLEThe UrlIdentityPermission example is similar to the ImperativeCAS example in that it discriminates on the basis of URL evidence, but the UrlIdentityPermission class is used instead of laboriously enumerating through the evidence information in a loop. The UrlIdentityPermission class is used to ensure that the only client code that may successfully call into the component's DoSomethingForClient method is the client from a specific URL. Again, this demonstrates a technique for limiting client code to only those clients that are considered trustworthy based on specified evidence. To test this component, we use two programs: TrustedClient and EvilClient . These two programs are virtually identical in every way in terms of source code. The only significant difference is the URL from which they originate. We now see how a URL is used to represent the protocol, path, and filename of each of these client programs. Here is the code for the EvilClient program. // EvilClient .cs using System; using System.Security; class EvilClient { static void Main(string[] args) { //NOTE: need ref to ImperativeCASComponent.dll //try to call on the component try { UrlIdentityPermissionComponent. DoSomethingForClient (); } catch (SecurityException se) { Console.WriteLine("Error: " + se.Message); } } } When you run this EvilClient program, you will see the following output. Note that it does in fact throw an exception, indicating that it originates from a URL that differs from the one that is trusted by the server component assembly. As you can see, the specific exception thrown is a SecurityException , and its message property states that a request for the UrlIdentityPermission failed. When we see the code in the UrlIdentityPermissionComponent assembly, we will see why this occurs. Error: Request for the permission of type System.Security.Permissions.UrlIdentityPermission, mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 failed. Here is the source code for the TrustedClient program. As you can see, it is indeed virtually identical to the EvilClient code. But its output is surprisingly different. // TrustedClient .cs using System; using System.Security; class TrustedClient { static void Main(string[] args) { //NOTE: need ref to ImperativeCASComponent.dll //try to call on the component try { UrlIdentityPermissionComponent. DoSomethingForClient (); } catch (SecurityException se) { Console.WriteLine("Error: " + se.Message); } } } Here is the output that results from running the TrustedClient program. This time, no exception is thrown, so the error message is not displayed. The only line of output results from a Console.WriteLine method in the UrlIdentityPermissionComponent assembly, showing that it is quite happy with being called by this particular client program. Client call permitted Why do these apparently identical programs result in such different behavior? To understand this, let's look at the code in the UrlIdentityPermissionComponent.dll assembly. As you can see, we create the UrlIdentityPermission object, specifying the desired (i.e., trusted) client application, and then call the Demand method. Then, it simply goes about its business, and, if the client does not match the URL from which the trusted client should originate, then a security exception is automatically thrown. There is no fussing about with an Evidence object or iterating over an enumeration in a loop. You just let CAS do its job. // UrlIdentityPermissionComponent .cs using System; using System.Windows.Forms; using System.Security.Permissions; public class UrlIdentityPermissionComponent { public static void DoSomethingForClient() { UrlIdentityPermission urlidperm = new UrlIdentityPermission( "file://C:/... /TrustedClient.exe"); urlidperm.Demand(); //if we got this far then all is OK Console.WriteLine( "Client call permitted"); } } In the actual source code, the path in the previous listing is fully specified in the UrlIdentityPermission object. Since it is too long to display properly in this book, it has been trimmed down in size using three-dot notation. In this code listing the Demand method ensures that the CLR will detect any mismatch between the desired permission and the actual current permission in effect at runtime. Recall that when an assembly is loaded, the CLR reviews all the available host evidence and assigns the assembly all of its identity permissions based on that evidence. The example we have just looked at used the UrlIdentityPermission class, which is only one of the available identity permission classes. We could just as easily have used any of the following identity permission classes. Recall that these identity permissions refer to where the assembly came from (site, URL, and zone) or who digitally signed it (strong name and publisher).
THE FILEIOPERMISSION EXAMPLELet's turn our attention now to an example of a permission class that is not an identity permission. The FileIOPermission example shows how the FileIOPermission class can be used to control file IO operations in a method. The FileIOPermission directory contains three projects: FileIOPermission , which is an EXE, as well as AttemptIO and AvoidIO , which are DLLs. The FileIOPermission program creates a FileIOPermission object that represents unrestricted file access, but it then calls on its Deny method, effectively disallowing all file IO privileges. It then calls into the DoNoFileIO and DoFileIO methods of the two DLL assemblies. Although the Deny method is called in a different assembly than where the IO will actually be attempted, the call stack will be walked back up to the Main method, where the security system will discover that the file IO permission is denied, causing a security exception to be thrown. The source code for all three projects follows . //FileIOPermission.cs //must add ref to AvoidIO.dll //must add ref to AttemptIO.dll using System; using System.IO; using System.Security.Permissions; using System.Security; class FileIOPermissionExample { public static void Main() { FileIOPermission fiop = new FileIOPermission( PermissionState.Unrestricted); fiop. Deny (); try { AvoidIO avoidio = new AvoidIO(); avoidio. DoNoFileIO (); AttemptIO attemptio = new AttemptIO(); attemptio. DoFileIO (); } catch(SecurityException se) { Console.WriteLine(se.Message); } } } //AvoidIO.cs using System; public class AvoidIO { public void DoNoFileIO() { Console.WriteLine("DoNoFileIO called..."); Console.WriteLine("Nothing written."); } } //AttemptIO.cs using System; using System.IO; public class AttemptIO { public void DoFileIO() { Console.WriteLine("DoFileIO called..."); String text = "Here is some data to write"; FileStream fs = new FileStream( "outputdata.txt", FileMode.Create, FileAccess.Write); StreamWriter sw = new StreamWriter(fs); sw.Write(text); sw.Close(); fs.Close(); Console.WriteLine( "Written to outputdata.txt: " + text); } } In the previous example, using the UrlIdentityPermission class, we explicitly called the Demand method to determine whether or not we had that particular permission before proceeding. In contrast, in the above source listings, we never call the Demand method explicitly. This is because the FileStream class that we are using calls the Demand method for us where necessary. In general, the predefined permission classes that do not relate to identity evidence do not require you to call the Demand method in your own code. The .NET Framework generally knows when to do that for you. You might want to call the Demand method yourself if you would like to test for the permission earlier to improve efficiency or simplicity. If you implement your own custom permission classes, you must take on the responsibility to make the appropriate calls on the Demand method where necessary. The output from running the FileIOPermission program follows. As you can see, the method that did not attempt IO worked fine, but the method that attempted file IO threw a FileIOPermission exception. DoNoFileIO called... Nothing written. DoFileIO called... Request for the permission of type System.Security.Permissions. FileIOPermission , mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 failed . |