How I saved my company from piracy by writing to CONTEXT_INFO from my ASP.Net application
Posted by Guirec in .net , C# , sql server
Ok, that one is a bit hardcore but helped me a few times...
Here I'll demonstrate how with a very little impact on your code you can pass a variable from C# code to Sql Server to make it available as a session variable during your connection lifetime.
Let's try and explain through a concrete example:
You are architect at Colors, you know, the multi-all company (multi-national, multi-technology, multi-department, multi-cultural, multi-millionaire, etc...) and at Colors all applications have the following use case defined:
- Mr Pink: "Cool! A Manager should be able to know who did what on every single piece of data... anywhere, anytime"
- Me: "What for?"
- Mr Brown: "Security reason! Just do it!"
- Me: "And what about the existing half-a-dozen applications we maintain?"
- Mr Pink: "Cool! I told them every single piece, anywhere, anytime should not be a problem..."
Mmmm...looks like we have a slight issue. Ok... let's go then!
In my opinion it's better doing this at database level rather than code : faster, less bandwidth consumption, etc...
I'll probably explain my solution to that "requirement" in a later post but the thing to do is, somehow, log all CUD operations (CRUD with no Read) with their respective operator identifier, yes UserId if you prefer....
Unfortunately your half-a-dozen applications makes usage of Forms authentication which means:
- Username and Password hash sits in the db
- The application process runs and connects to the database through a Service Account (IUSR_MyMachine, ASPNet or whatever account the IT guy, Mr Grey, has chosen when setting up the application in the IIS box).
Too bad: sql server won't be able to tell you who is connected to your database...
4 options then:
- Kill mr Pink. Could be fun but you are not sure you'll be allowed to use a computer in jail. So, geeky, as you don't want to spend the rest of your days locked in a cell without your best friend: don't choose that one!
- Randomize the UserId in the CUD operations log: by the time someone discovers you have done that then you might be far...
- Ask your offshore team to rewrite the DAL of your applications so that you only use stored procedures which accepts a UserId (or SessionId, or whatever allows you to identify your user) as an input parameter. Why not? Need to open a work item in TFS + follow it up. That's what I call a boring job. Plus I don't like opening this kind of work items in TFS: bad for my stats...
- Use the Sql Server context. What's that? Every time you open a connection to Sql Server you participate in a session. That session can hold a 'little' piece of context data in CONTEXT_INFO. As the doc says, CONTEXT_INFO is a 128 bytes long binary values. We could probably use that to store the UserId of the connected user when we open a connection from C# code and then reuse it into a trigger to log data changes. How can I do that???
Populate the context info from your .Net code by :
- Transform your data into a byte array
- Convert it to an hexadecimal string (see HexadecimalConverter class)
- On each sql server connection opening execute a command to set the context info
In this example, I use a COMB Guid (see great article by Jimmy Nilsson) as my UserId and a Linq DataContext to access my SQL Server DB.
public partial class myDataContext { partial void OnCreated() { // subscribes to the data context connection StateChange event this.Connection.StateChange += new StateChangeEventHandler(Connection_StateChange); } void Connection_StateChange(object sender, StateChangeEventArgs e) { // When connection opens if (e.CurrentState == ConnectionState.Open) { // use the standard ExecuteCommand query provided by // DataContext to set data into the Sql Server base.ExecuteCommand( String.Format ( "SET CONTEXT_INFO 0x{0}", // the current UserId is provided by an extension // methods as well as the conversion to hexa string this.UserId().ToByteArray().ToHexadecimalString() ) ); } } }
Now you are ready to consume the context info from any stored procedure, trigger or whatever by writing the following code:
// converts back the 128 bytes long binary values into a Guid select convert(uniqueidentifier, context_info())