- Types of .NET Security
- Using Imperative Security
- Using Declarative Security
- Advantages and Disadvantages of the Two Security Implementations
Using Imperative Security
You'll use imperative security most often to protect specific resources, such as a file, folder, or hard disk. Imperative security offers flexibility in describing precisely how to apply security within the application. Listing 1 shows a typical example of using imperative security to protect a file. (The example leaves out obvious error checking, such as validating the existence of the file, for the sake of clarity.)
Listing 1: Using Imperative Security to Restrict File Access
private: System::Void btnDeny_Click(System::Object * sender, System::EventArgs * e) { FileIOPermission *FIOP; // Permission object. Stream *FS = NULL; // A test file stream. // Create the permission object. FIOP = new FileIOPermission(FileIOPermissionAccess::Read, "D:\\Temp.txt"); // Allow access to the resource. FIOP->Deny(); // Try to access the object. try { FS = new FileStream("D:\\Temp.txt", FileMode::Open, FileAccess::Read); } catch(SecurityException *SE) { MessageBox::Show(SE->Message, "File IO Error", MessageBoxButtons::OK, MessageBoxIcon::Error); return; } // Display a success message. MessageBox::Show("File is open!", "File IO Success", MessageBoxButtons::OK, MessageBoxIcon::Information); // Close the file if opened. FS->Close(); }
The code begins by declaring a permission. The .NET Framework provides a wealth of permission types that you can view as part of the System.Security.Permissions namespace help topic.
In this case, the specific permission used is FileIOPermissionAccess. Notice that you must specify the resource you want to work with when you create this permission. Here, the code specifies a file on disk, but you can protect a folder or the entire disk drive with equal ease. After the code creates the FileIOPermissionAccess object, it uses the Deny() method to declare that the code can't access the file. If you were to use the Assert() method, the code would gain access to the file, even if the calling code lacks such permission.
This demonstrates how code access security works. The code can't access the file even when the user has appropriate rights. The remainder of the code checks the effectiveness of the permission, by trying to access the file. In this case, you'll see an error message like the one shown in Figure 1.
Figure 1: Denying access to a resource will result in a security error message similar to this one.
Role-based security requires the use of a principal. You need to determine who is currently using the application, and then decide his access as part of a role. Listing 2 shows an example of all the elements you need to work with role-based security.
Listing 2: Using Role-based Security
private: System::Void btnTest_Click(System::Object * sender, System::EventArgs * e) { WindowsPrincipal *MyPrincipal; // The role we want to check. AppDomain *MyDomain; // The current domain. StringBuilder *Output; // Example output data. Array *RoleTypes; // Standard role types. Int32 Counter; // Loop counter. String *RoleName; // The name of the role. // Set the principle policy for this application. MyDomain = Thread::GetDomain(); MyDomain->SetPrincipalPolicy(PrincipalPolicy::WindowsPrincipal); // Get the role and other security information for the current // user. MyPrincipal = dynamic_cast<WindowsPrincipal*>(Thread::CurrentPrincipal); // Get the user name. Output = new StringBuilder(); Output->Append("Name: "); Output->Append(MyPrincipal->Identity->Name); // Get the authentication type. Output->Append("\r\nAuthentication: "); Output->Append(MyPrincipal->Identity->AuthenticationType); Output->Append("\r\n\r\nRoles:"); // Create an array of built in role types. RoleTypes = Enum::GetValues(__typeof(WindowsBuiltInRole)); // Check the user's role. for(Counter = 0; Counter < RoleTypes->Length; Counter++) { // Store the role name. RoleName = RoleTypes->get_Item(Counter)->ToString(); Output->Append("\r\n"); Output->Append(RoleName); Output->Append(":\t"); try { // Store the role value. Output->Append(MyPrincipal->IsInRole(RoleName)); } catch (Int32) { // Catch unknown RID errors. Output->Append("Unknown"); } } // Output the result. MessageBox::Show(Output->ToString(), "User Role Values", MessageBoxButtons::OK, MessageBoxIcon::Information); }
In this example, the code begins by obtaining the principal, or caller, for the current thread by using the Thread::CurrentPrincipal property. Notice that you must set the principal policy with the MyDomain->SetPrincipalPolicy() method before you can use this property, or some information will be missing.
As the code shows, some information is directly accessible from the WindowsPrincipal object. This information includes the caller's identification (name, in the case of a user) and the method used to authenticate the caller.
The example retrieves role-based security information for the caller (which is going to be you) for all of the built-in roles listed in the WindowsBuiltInRole enumeration. Normally, you'd want to find out just one or two roles, but the example goes a little farther. The code begins by adding the role name to a StringBuilder object. It then adds one of the values: True, False, or Unknown. If you're part of the current role, then the code adds True to that name in the StringBuilder object. Likewise, if you don't belong to the role, then the code adds False. The code adds unknown when the MyPrincipal->IsInRole() method generates a relative identifier (RID) error. The role might not exist on your machine (you might have disabled it), so the status of this role is unknown.
You can check for a caller's role using the IsInRole() method in a number of ways. The easiest method to use, when working with built-in roles, is the WindowsBuiltInRole enumeration: IsInRole(WindowsBuiltInRole::User). When you check for a custom role, using a string works best: IsInRole("MyCustomRole"). Avoid using the third technique, whereby you supply a RID in integer form, as this method is error-prone at best.