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; } } } |
| 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()); } |