|
|
.NET Security and AttributesA Cool Approach to Security | | Like just about everything else, Security in .NET is a lot different than it was in the old days. I discussed the basics of security here and if you aren't familiar with Declarative and Imperative Security, you may want to read it before continuing. I want to show a practical example of using Declarative Security and Attributes to make your application more secure and do it the .NET way.
Let's say that you wrote a generic method to open a file, read the data out of it, and do whatever with it. However, assume that one of the requirements is that your program can't read anything in a given directory, say WINNT, but it can write to it, as long as it doesn't overwrite an existing file. Back in the old days, you might do something like this:
1) At the beginning of the function, look at the string being passed in which represents the path and filename of the file you want to open.
2) Parse out the path and verify that it's not a prohibited path.
3) If a prohibited path is found, alert the user and exit, or just exit, or ask the user to select another file
Now, if at first the requirement is simply that your program can't access the directory, this is straightforward enough. On the other hand, if you can allow the user to write to the directory but not read from it, then you are going to have to do some branching and can't just reject the request. A lazy programmer may just decide that they don't want to go through the hassle and just not let the user touch the given directory, but that could unnecessarily burden the user and if there was a compelling reason for writing to that file, the whole logic of your program would need changed. Most projects tend to grow in complexity so a scenario such as this isn't hard to imagine. Ok, easy enough..you write the branch logic and off you go. Now, let's assume that next week you were asked to cutoff Write Access to C:\SomeDirectory, C:\SomeOtherDirectory, and C:\AnotherDirectoryAllTogether. In addition, you were to cut off Read Access to C:\ReadDirectory. All of a sudden, a small code snippet would be littered with Branching statements, and in all likelihood, you'd really need to test it to make sure that you covered all of your bases. Assume for a second that you were actually going to test for a directory's existence, parse the file name, and then determine what could and couldn't be done. It doesn't take much imagination to see that properly implementing such a thing could easily run you a few hundred lines if you had enough directories (and files) and if read/write access was different for them.
In comes attributes. All you need to do with attributes is mark the code with a FileIOPermission attribute and off you go. Since you can add as many attributes as you want to a code snippet, you can add in functionality without TOUCHING ONE LINE OF CODE withing your function. Moreoever, you wouldn't have to adjust your logic AT ALL. Compare this to the old school way where it'd take a few hundred lines to implement a complex scenario, and the value of attributes becomes readilty evident.
Let's take a look at how we'd do this:
public class FileHandler
{
public FileHandler(){}
[FileIOPermission(SecurityAction.Deny, Write=@"C:\AnotherDirectoryAltogether")]
[FileIOPermission(SecurityAction.Deny, Write=@"C:\SomeOtherDirectory")]
[FileIOPermission(SecurityAction.Deny, Write=@"C:\SomeDirectory")]
[FileIOPermission(SecurityAction.Deny, Read=@"C:\ReadDirectory")]
[FileIOPermission(SecurityAction.Deny, Read=@"C:\WINNT\System32")]
public bool OpenAndReadFile(string fileName, int fileSize)
{
bool Results = false;
using(FileStream fs = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
byte[] fileBytes = new Byte[fileSize];
int bytesRead = 0;
try
{
while((bytesRead = fs.Read(fileBytes, 0 ,fileBytes.Length)) > 0 )
{
}
Results = true;
}
catch(System.Exception ex){
Debug.Assert(false, ex.ToString());
Results = false;}
return Results;
}
}
} |
As you can see, this takes 5 lines of code to implement, and by virtue of the attributes, we can ensure that our goals are met. If we need to change access levels, add new directories or remove some, it's as easy as manipulating the attributes. If you really envision the code it would take to have your fuction implement Write/Read access as stated above, it's really amazing what can be done with declarative security. Moreoever, look at the code from the caller:
try
{
FileHandler f = new FileHandler();
bool result = f.OpenAndReadFile(@"C:\WINNT\System32\IAS\dnary.mdb", 100);
}
catch(System.Exception ex)
{
Debug.Assert(false, ex.ToString());
} |
I don't have to keep track of the directories (which is what I'd have to do if I wanted to ensure that the off limites files or directories weren't passed in. So what we get using this method is a much more flexible approach, one that's much less error prone, one that can be changed with little effort (and isn't likely to break existing code) and one that's much easier to understand. Now, how you actually implement your OpenAndRead class and how you want return values to be returned is up to you. My main point is simply to show how this method can allow you to write code that will make life easier for everyone.
As an aside, Security is but one area where attributes can make your life much easier. Moreover, there are some things that are next to impossible or require UGLY solutions at best if they aren't used. The facts that you can pile them on top of each other and that you can create your own makes them even more powerful. If you aren't using any custom attributes (or any attributes at all), I'd highly recommend taking some time and learning more about them. Jason Bock & Tom Barnaby's book is an excellent place to start.
|
|