In this post I am going to show you an example of CardSpace and an Office application working together.
I know, I still owe you part II of the STS walkthrough; however I delayed this post for months, and I promised I would have done it this long weekend so I can't really skip it this time. I will write the part II of the STS post in the next days.
I don't know about you, but I spend a lot of time working with Office applications: Outlook & Word above all, but also OneNote, Excel... and they are really great. Typically I get data in and out of office by alt-tabbing through other apps, typically the browser. For example: how many times did you fiddle with cut&paste for inserting data from your Internet banking app to an Excel document of yours?
Sure, many home banking websites offer you to export data in Money or Quicken format, sometime even in plain CSV; but wouldn't it be great to be able to access the data directly while you're working in Excel itself? Of course, we would not want to trade ease of use with security: importing data should be convenient, no doubts about it, but it should also guarantee levels of security proportionate to the sensitivity of the data handled. Sounds like a good example scenario: let's build around it.
Let's assume that the website of our bank is ahead of the curve and allows its customers to sign in with personal cards for performing some simple, read-only operations (such as giving the list of all your accounts or the list of movements for a given account). The bank could then expose web services that would offer a programmatic interface to the above mentioned functions; such web services could be secured by requiring the user to pick up at invocation time the same personal card already registered for accessing the website. In that case, the resulting token would be used for enforcing message level security (while in the website case the token would simply be handled over a transport protected connection, with the exception of some lower value scenarios).
Once you have web services secured via CardSpace, you can access the same functionality from any client that is able to perform a web service call: we've seen how to take advantage of it via sidebar gadget, today we will examine how to do it via Excel.
The functional spec is simple. I want to be able to import in any Excel document the movement list of any of my accounts, without ever having to leave the Excel UI (apart from the identity selection itself, naturally). I would also like to maintain some decent architectural standards, so for example I would like to avoid having to shove anything in Excel.exe.config.
We'll do this in two steps
- Create a webservice that will serve movements lists and configure it for using CardSpace
- Create a service agent class that can invoke the service and marshal results through AppDomain boundaries
- Create an Excel addin that leverages the service agent for invoking the service and writes down the results in Excel
OK, three steps 🙂 here we go:
Create the web service
Visual Studio 2008 is really friendly when it comes to play with WCF. In this case I start our example by choosing File->New->web site->WCF Service. I am creating the solution in an existing virtual directory (pardon:application). That automatically creates all the files I need, including the svc. Let's modify the IService.cs & the Service.cs files (both found under the app_code folder, conveniently created by the project template) for performing some mockery of the functions we want to implement.
Here there's part of the interface (IService.cs):
True, I usually cut & paste the code instead of an image; but since I'm going to publish the complete example anyway, here I privilege the speed of the operation. BTW, there's really very little to see: IWSAccountInfo is a simple ServiceContract, which gives back a list of accounts (Balance) and given an account it gives back the list of movements occurred between two dates.
The service class is even less interesting, it's just a bunch of literals & hardcoded values. I'll spare you the details.
The changes we make to the web.config are more interesting. We go to the system.serviceModel and we modify Service for using wsHttpBinding with clientCredentialType equal to IssuedToken, the jargon we use in WCF when we want to pop up CardSpace; and we define a service behavior that tells the system which certificate should be used for identifying the service (in this case, we are using a selfsigned 'localhost'). We'll keep the mex endpoint, that will simplify generating clients for the service.
Great! Let's navigate to the mex page:
Very nice. But do we really believe that CardSpace will show up if we invoke this service? Luckily, we can find out in seconds: creating a test client with advanced ws-* capabilities is trivial in VS2008.
We can use the WcfTestClient tool (see this post for a more detailed explanation) for creating a dedicated client: for me the following command line worked, your installation may be different:
C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE>WcfTestClient http://localhost/BankSample/Service.svc?wsdl
That would result in the following window:
Double clicking on GetBalanceSheet reveals an Invoke button:
We press the invoke button, aaand...
Ta dah! We get the usual identity selector. We can choose any card we want, once we click send we'll use the resulting token (and the cert of the webservice) for securing the call: our test client will display the results.
Now, for the purposes of our example we may be already happy and move to the client part; but in fact, I am very bothered by the fact that I can choose any card for securing the call. The idea is that I should call the service with one specific card, the one already registered with the website: hence, I am going to add some simple logic that will guarantee we accept one specific card (note: if you are into SOA & web services and you'd like to handle identity info in your architecture, this is something that should get your attention).
I have seen a number of examples in which the authentication tests are performed in the method body; since they are just examples it's OK like that, but you should keep in mind that it is not very realistic. Embedding the authentication logic in the method itself tends to hinder the reusability of the code, and makes definitely difficult to change policies at deployment times. For that reason, I am going to put the test in a custom ServiceAuthorizationManager (for details about the WCF security pipeline and class hierarchy, see the excellent diagram that our sharp Jan Alexander made for MSDN). I won't go in the details here; let's just say that this allow you to decide at deploy time, via config, which authorization logic I want to assign to a service instance. I will ruin everything by hardcoding the values that identify the card I want to accept, but you get the idea. The solution is debatable, some friend in the WCF security team would like "doomed" requests to fail way earlier in the pipeline, but I personally believe this strikes a good balance between efficiency and maintainability.
It's so clear that it may be pseudocode (and to some extent, thanks to the code regions, it is ;-)). CheckAccessCore is the method that gets called when the service is invoked, waay before the execution reached the method body; it gets the OperationContext in input, that iis used for determine if the method should be executed (return true) or blocked (return false; the caller will be hit by an Access Denied exception). The contracted regions contain some utility functions (shamelessly borrowed from our friend TokenProcessor.cs. Hopefully we'll be able to send it to retirement soon :-)) which calculate the uniqueID (see this post for details) of the token used for securing the incoming request. My authentication logic is really trivial: a very ugly hardcoded string comparison, which tries to determine if the uniqueID iof the incoming token corresponds to the one of the card I wanted (the string value was obtained by a debug run using that card and saving the value of the call GetUniqueIDFromRequest(operationContext)).
Now that we wrapped our logic in a ServiceAuthorizationManager, assigning it to the service is as easy as adding a line to the service behavior in the web.config:
If you have the wcf test client still open from the former steps, you can use it for experiencing the new behavior; otherwise you can just regenerate it as described above.
You'll see that if you invoke the service with the right card everything work as expected,but if you use the wrong one... access denied.
That's great, we can declare our backend ready.
Create the service agent
Remember the post about CardSpace and the sidebar gadget? We had the same problem we have here: our service invoking logic is going to be hosted by a service we don't own, and one that already has an AppDomain in place. That makes using configuration files a bit challenging. In the above mentioned post I gave a pretty detailed explanation of the problem and its solution, I won't repeat it here. Let's just say that I am creating a class InnerServiceInvoker that can invoke out banking web service; and a class ServiceInvoker that creates a new AppDomain, with an arbitrary config, and executes an instance of InnerServiceInvoker in it. That allows us to provide arbitrary configuration to our client, without having to touch excel.exe.config. All classes that are traded through AppDomain boundaries need to be marked as serializable, and that means that you have to add the magic attribute to every datacontract you use in the serviceagent assembly. Also: there's a fair amount of reflection involved in the process, so it's just faster to put the assembly in the GAC. Again, please refer to the post about the sidebar gadget for an extensive explanation; what's important is that we now have a class that our addin can use for invoking the web service using an arbitrary configuration.
Create the addin
Now comes the exotic part. Actually, developing software for Office is brain dead easy with Visual Studio 2008; you have a number of templates to choose from, and they all make your life as easy as if you'd be writing the most classic winform app.
Let's choose Excel Add-in. What we get is a barebone addin, to which we can add some UI element by choosing add->new item in the project's contextual menu:
I like the idea of the ribbon, since it's something that was not available in previous versions; also, I find it more discreet for our function than a full fledged task pane.
From now on, we can easily create our UI by point and click. Namely, I want:
- A button that calls GetBalance service method and stores the resulting list of accounts in a combo box
- Another button that copies in the current sheet the movements of the account (obtained by calling GetAccountMovements) selected in the combo box
What's left? We have just to add a reference to our service agent class; then we can double click on the buttons and fill in the corresponding event handlers. For example, below there's the code for getting the list of accounts:
The part circled in red instantiates our service agent. The part in blue performs the call. Everything else has to do with the UI. Easy, right?
Side note: why didn't I change button1 to something more meaningful? Judge Fudge would say "I am far too busy being delicious", I more humbly admit that I am lazy, sloppy and I often suffer the consequences of that 🙂
The logic for copying the movements is really easy as well:
The invocation is in the line under the red ellipse; everything else is logic needed for transferring the results in Excel (I am sure that all my colleagues focusing on Office are reaching for the sack under their seat by seeing how I did it; apologies guys, if there's a more proper way of manipulating celkl values please let me know :-)) .
Running the solution
We're finally ready to test what we've done! Set the excel addin project as the startup project and hit F5: Visual Studio is nice enough to handle for us security settings & registration steps, so that we can literally have the F5 experience.
Your usual Excel window will open up and... ta dah! There is a new tab on the right.
You won't be surprised to find in it the UI we just designed:
Let's try Get Accounts.
Yes, you recognize it. We pressed the Get Account button and we got prompted by CardSpace. Choose the card you configured in the ServiceAuthorizationManager, and click send.
Nice! Since we chose the right card the call worked, and we got the dropdown populated with the account numbers (wrong card would have gotten access denied, exactly like with the wcf test client.). In this example we are not destroying the proxy, so from now on (for the duration of the token) we won't be prompted again every time we invoke the service. Note that, if we don't like this behavior, we can easily change it. If we'd have to call more services that share the same security requirements, we may even use the token caching technique demonstrated here: after all, we went through the pain of creating the service agent exactly because we wanted the freedom of working on the config.
Anyway, let's make the final step: by pressing Copy Movements we should securely receive the movements of the account #1000 and visualize them in the current worksheet.
And it worked, as expected.
What did we do?
- we created a service, configured it for using cardspace and tested it on the fly
- we added a custom ServiceAuthorizationManager that blocks all calls that are not being protected by a particular card
- we created a service agent that, by playing with AppDomains, allows us to apply any configuration file regardless of the host
- We created an excel addin and a ribbon that leverages the service agent for establishing secure communications with the web service of an imaginary bank, and that can deliver the results of those communications directly into the Excel UI
I don't know about you, but I find this wicked cool. Technically it is really no different than what we made for the sidebar gadget; however, think of the possibilities! Empowering your users to access your data directly from the applications they are already using, and still maintain the security standards that you want... in fact, in this example I used simple personal cards: but nothing prevents you from using managed cards with multiple authentication factors. And what I really love of all this is that once you made the investment of card-enabling your backend, you can access the data from the most diverse client types (browser, winforms, wpf, sidebar gadgets, office...) without changing a line on the server and still retain consistent user experience and security levels.
In the next days I'll post the file as attachment; if you have questions, feel free to drop me a line