Imperative security is where you create instances of permissions and then use those instances to Demand() or Assert() those permissions, and much more. A demand is used when you want to tell the CLR that if there is code on the call stack that does not have the indicated permission level, a SecurityException will be thrown. An Assert is where your code asserts a specific permission level. All code on the stack after the assert will actually have the permission your code asserted, regardless of the assembly's original permission level. You must be very careful when asserting permissions because permission assertions can be used to create holes and exploits in CAS.
Creating an instance of a permission works just like creating an instance of any other class, as shown in the following code:
FileIOPermission filePerm = new FileIOPermission(FileIOPermissionAccess.Write, AppDomain.CurrentDomain.BaseDirectory);
The interesting thing to note here is that in order to create an instance of the permission, the code itself must have that permission. In other words, you must have a permission before you can demand that all callers on the stack prior to your code have that permission.
To create a sample that illustrates both Imperative Security programming as well as the demand of permissions on a call stack, start off by creating a new console application. In VS 2005, right-click the project, choose Properties, and then choose the Signing tab. Click the Sign This Assembly checkbox, and then choose New from the drop-down box to create a new strong name key pair. Then, create a Class Library project and perform the same steps to create a new SNK (strong-name key) file and sign the class library. Add a reference from the console application to the class library.
With this in place, you can go into the .NET Framework 2.0 Configuration tool in the Runtime Security Policy area. Create a code group that contains only the strong name for the console application, and a code group for the class library. The console application should have just Internet trust, whereas the class library should be fully trusted. Make sure that the console application's code group has a check next to "This policy level will only have the permissions from the permission set associated with this code group"; otherwise, the code group will not enforce any restrictions.
At this point, you should have two code groups at the machine level beneath the All_Code parent group. This is an excellent simulation of a partially trusted assembly attempting to call a fully trusted assembly. By default, the .NET Framework does not allow partially trusted assemblies to call code from fully trusted assemblies. To counteract this, you can add the assembly-level attribute AllowPartiallyTrustedCallers to the assembly as shown in the following code:
Allowing Partially Trusted Callers
You should take extreme care when allowing partially trusted callers into your assembly. If your assembly is fully trusted, and you allow partially trusted callers without making the appropriate permission demands, your assembly can be used as a security exploit or hole. If you can't think of a reason why you would want a partially trusted caller to use your assembly, leave things as they are and don't worry about checking permissions explicitly, because your assembly can't be called without a fully trusted client. On the other hand, if you are creating an API that does high-permission things on behalf of low-permission clients (creating and manipulating proprietary files, for instance), you will need to allow partially trusted callers, and then explicitly demand every permission you require of calling clients either imperatively or declaratively.
With all this in place, change the Class1 definition in your class library to the following code in Listing 14.1.
Listing 14.1. Class1.cs
The preceding code will create a text file. Without the permission demand, then the partially trusted caller would be able to create the file, even if the caller had no File I/O permission. Now modify the Program.cs from your console application to look like the code shown in Listing 14.2.
Listing 14.2. Program.cs
With the current security settings, we get the following output when executing the code:
Security exception occurred, System.Security.SecurityException: Request for the permission of type "System.Security.Permissions.FileIOPermission, mscorlib, Vers ion=188.8.131.52, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed. at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMa rk& stackMark, Boolean isPermSet) at System.Security.CodeAccessPermission.Demand() at System.AppDomainSetup.VerifyDir(String dir, Boolean normalize) at System.AppDomain.get_BaseDirectory() at MoreTrustedAssembly.Class1.CreateFile() in C:\Documents and Settings\Kevin \My Documents\Writing\SAMS\C# 2005 Unleashed\14\Code\ImperativeSecurity\MoreTrus tedAssembly\Class1.cs:line 17 The action that failed was: Demand The type of the first permission that failed was: System.Security.Permissions.FileIOPermission The Zone of the assembly that failed was: MyComputer
Now all you need to do is go back to the code group you created for the console application and change the trust level from Internet to FullTrust. This will result in no output, and you'll see that the file myfile.txt has been created.
Enforcing Identity Imperatively
You can use the PrincipalPermission class to enforce identity constraints on the user. It is extremely important to remember that the user comes directly from System.Threading.Thread.CurrentPrincipal. You may notice that this value isn't set by default in a console application, so you have to do it manually. The code shown in Listing 14.3 shows how you can use the PrincipalPermission class not only to enforce specific constraints, but also to use it to create a union or intersection of multiple identity constraints, such as requiring that the caller be both part of the Administrators group and the Users group.
Listing 14.3. Imperative Identity Enforcement
If you pass a null to the name parameter of the PrincipalPermission constructor, the permission will not require anything of the user's name. Note that this name requires the domain or computer name prefix. Additionally, if you pass a null to the Role parameter, the permission will not require any explicit role membership. If you pass a null to both name and role parameters and just pass true to the third parameter, the permission will indicate that the calling user simply needs to be authenticated.