September 2010 - Posts
Some weeks ago I received a review copy of Silverlight 4 in Action by Pete Brown. Reviewing this book took some time as it weighs in at a hefty 798 pages, who ever said that Silverlight was a small products? That is a lot of material but then the book doesn't assume any prior knowledge of Silverlight at all.
The book is divided into 25 chapters in 3 different sections.
The first part, consisting of 10 chapters, is titled "Introducing Silverlight". As the name suggests this covers the basics of Silverlight development. This section is mostly targeted at developers new to Silverlight and covers subjects like what XAML is, how the Silverlight plug-in works with the browser, basic layout and the Silverlight controls and how they work with things like templates.
The second part is titled "Structuring your application". This section is divided into 7 chapters and build on top of the first part. In this section there are chapters about data binding, input validation, communicating and even about subjects like MVVM and WCF RIA services. Some of these topics, like for example MVVM, are quite large to begin with and the definitive word hasn't been said about it. In these cases the book is a nice point to get started but no more than that and getting additional information on the Internet is pretty much a requirement. In most cases, like validation. Pete has a pretty solid coverage of the material the book will help you understand what you need to know.
The third part is titled "Completing the experience" and consist of a final 8 chapters. This section covers subjects like printing, working with media like smooth streaming or using the webcam and microphone.
There are also chapters on animations, using resources and creating custom controls.
So who is this book for?
With a book this big covering that many topics the book is really targeted at a wide range of developers. It does assume the reader is familiar with C# and the .NET framework but assumes no familiarity with Silverlight at all. So if you are an experienced .NET developer wanting to get into Silverlight this is a good book to start with using part 1. You you are already a Silverlight developer moving from Silverlight 3 to Silverlight 4 this book will also be quite helpful. You will find a lot of material you are already familiar with but also a lot of new material like web cam support and WCF RIA services or extended validation options. In that case this book would be a useful reference to keep handy and read specific chapters from par 2 or 3 when needed.
All in all I would recommend getting this book if you are serious about Silverlight 4 development!
Enjoy!
In my previous blog post I demonstrated how to secure a workflow service using the Windows Identity Foundation. With this in place we only allow users that are trusted by the STS Dominick Baier wrote. That is nice but in some cases we might not want all users to be able to perform all actions.
How can we check for individual users against a specific action?
Using an STS we don’t get the user credentials from the client application but we do get a lot of information in the form of security tokens from the STS my means of a SAML header. When we are using Windows Identity Foundation the place to check if a user can use a specific resource is using a ClaimsAuthorizationManager. This ClaimsAuthorizationManager is very simple and it contains just a single method we need to override, the CheckAccess(), which returns a boolean if the user is allowed.
The CheckAccess() function is passed an AuthorizationContext which lets us determine the resource requested, the URL of the workflow service in this case, and details about the user. So suppose we only want to let users with the role “Managers” into our service we would need the following code:
public class MyServiceClaimsAuthorizationManager : ClaimsAuthorizationManager
{
public override bool CheckAccess(AuthorizationContext context)
{
var result = true;
var resource = context.Resource.First();
if (resource.Value == "http://localhost:1533/Service1.xamlx")
{
result = (from i in context.Principal.Identities
from c in i.Claims
where c.ClaimType == ClaimTypes.Role
select c).Any(r => r.Value == "Managers");
}
return result;
}
}
The Resource property contains whatever we want to access and the Principal property contains the details about the user wanting access. In this case the Thinktecture provides us with the roles the user provides.
We also need to register this in the web.config file using the <microsoft.identityModel><service> section. This can be done by adding the <claimsAuthorizationManager> element as follows:
<microsoft.identityModel>
<service>
<audienceUris>
<add value="http://localhost:1533/Service1.xamlx" />
</audienceUris>
<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<trustedIssuers>
<add thumbprint="7974900A2BB2829BE987C17D2F4503F07C321032" name="http://sample.thinktecture.com/trust/stsdm" />
</trustedIssuers>
</issuerNameRegistry>
<claimsAuthorizationManager type="MyService.MyServiceClaimsAuthorizationManager,MyService"/>
</service>
</microsoft.identityModel>
With this in place the student can no longer send requests to the workflow service but the manager is free to do so.
Authorizing users based on our own data
So far we have been allowing a user in based on the role data for that user. That works fine but suppose we want to use a very different criteria, for example the office location he or she is based in?
The STS is free to add extra claims as it sees fit and we can use any claim we want to authorize users. And the STS Dominick Baier wrote also contains a claim for which office the user is based in. Suppose we only want to allow users based in the Zoetermeer office to use our web service we could change our ClaimsAuthorizationManager to the following:
using System.Linq;
using Microsoft.IdentityModel.Claims;
namespace MyService
{
public class MyServiceClaimsAuthorizationManager : ClaimsAuthorizationManager
{
public override bool CheckAccess(AuthorizationContext context)
{
var result = true;
var resource = context.Resource.First();
if (resource.Value == "http://localhost:1533/Service1.xamlx")
{
result = (from i in context.Principal.Identities
from c in i.Claims
where c.ClaimType == "http://sample.thinktecture.com/claims/office"
select c).Any(r => r.Value == "Zoetermeer");
}
return result;
}
}
}
Using this query against the custom http://sample.thinktecture.com/claims/office claim we only allow users from the Zoetermeer office. Nice as our service has nothing to do about managing user locations, all of that is done centrally my the STS [:)]
Enjoy!
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu
The way security is often still handled these days with each application keeping track of their own users is somewhat dated. Some form of Federated security, where a single separate server is responsible for the security of a whole series of applications, is the way to go. On the internet there are plenty of examples of this with applications using things like OAuth and leaving their security to others. In windows the preferred form of federated security is through Windows Identity Foundation and it is real easy to secure an ASP.NET site or WCF service using Windows Identity Foundation.
How about securing a workflow service?
As a workflow 4 service is just another WCF service securing it is just as easy as the steps below will demonstrate.
Before we start we need to install Windows Identity Foundation and the related WIF SDK using the two links provided.
To start with I created a small workflow service and a simple client application. No security yet and the client works as expected.
The console app is real simple and looks like this:
static void Main(string[] args)
{
try
{
var proxy = new ServiceClient();
var data = proxy.GetData(42);
Console.WriteLine(data);
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex.Message);
Console.ResetColor();
}
Console.ReadLine();
}
The service web.config is also standard and pretty short:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<bindings />
<client />
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
This just works as expected and when I run I see the following output:
Securing this service using Windows Identity Foundation
First thing we need when we want to use WIF is a Security Token Service which will handle the actual security checks and provide the application with the required claims. There is an add-on for Active Directory on Windows 2008 we could use but in this case I am going to use the StarterSTS Dominick Baier wrote. Instead of hosting this myself I am going to use a publicly available instance hosted here by Thinktecture.
Adding the STS to the service is quite easy. With the WIF SDK another VS2010 menu option and wizard was installed to guide us through the process. One important thing is that VS2010 must run as administrator else this wizard will fail to complete.
The first page wants to know about the config file and the service URL used.
In the next page select existing STS and enter the address where the STS federation metadata is located, in the case of the Thinktecture StarterSTS this is https://identity.thinktecture.com/stsdm/FederationMetadata/2007-06/FederationMetadata.xml
Next specify to use encryption, using a default generated certificate will do just fine now.
And that is all we need, click through and select finish. If Visual Studio isn’t run in admin mode this will result in an error, if this happens just restart VS2010 with admin privileges and do it again.
After this wizard has completed the web.config file will contain quite a bit more data and look something like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</configSections>
<location path="FederationMetadata">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
<system.web>
<compilation debug="true" targetFramework="4.0">
<assemblies>
<add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>
</compilation>
</system.web>
<system.serviceModel>
<bindings>
<ws2007FederationHttpBinding>
<binding>
<security mode="Message">
<message>
<issuerMetadata address="https://identity.thinktecture.com/stsdm/users/issue.svc/mex" />
<claimTypeRequirements>
<!--Following are the claims offered by STS 'http://sample.thinktecture.com/trust/stsdm'. Add or uncomment claims that you require by your application and then update the federation metadata of this application.-->
<add claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" isOptional="true" />
<add claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" isOptional="true" />
<!--<add claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" isOptional="true" />-->
<!--<add claimType="http://sample.thinktecture.com/claims/office" isOptional="true" />-->
</claimTypeRequirements>
</message>
</security>
</binding>
</ws2007FederationHttpBinding>
</bindings>
<client />
<behaviors>
<serviceBehaviors>
<behavior>
<federatedServiceHostConfiguration />
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true" />
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<!--Certificate added by FedUtil. Subject='CN=DefaultApplicationCertificate', Issuer='CN=DefaultApplicationCertificate'.-->
<serviceCertificate findValue="CB484E11B065E559BB9D7221F0178E3C12848381" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<extensions>
<behaviorExtensions>
<add name="federatedServiceHostConfiguration" type="Microsoft.IdentityModel.Configuration.ConfigureServiceHostBehaviorExtensionElement, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</behaviorExtensions>
</extensions>
<protocolMapping>
<add scheme="http" binding="ws2007FederationHttpBinding" />
</protocolMapping>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
<microsoft.identityModel>
<service>
<audienceUris>
<add value="http://localhost:1533/Service1.xamlx" />
</audienceUris>
<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<trustedIssuers>
<add thumbprint="7974900A2BB2829BE987C17D2F4503F07C321032" name="http://sample.thinktecture.com/trust/stsdm" />
</trustedIssuers>
</issuerNameRegistry>
</service>
</microsoft.identityModel>
<appSettings>
<add key="FederationMetadataLocation" value="https://identity.thinktecture.com/stsdm/FederationMetadata/2007-06/FederationMetadata.xml" />
</appSettings>
</configuration>
The most important part is the <microsoft.identityModel> section which contains the Windows Identity Foundation setup.
If we run the client now we will see an exception with the following text:
Content Type text/xml; charset=utf-8 was not supported by service http://localhost:1533/Service1.xamlx. The client and service bindings may be mismatched.
No big surprise as the client isn’t aware yet of the new security measures and still sends a request using the BasicHttpBinding. To get the client to work we need to do an update service reference on the generated proxy. This will generate a new customBinding with all required Windows Identity Foundation settings.
Because the STS Dominick Baier has running supports multiple ways the user can authenticate, either username/password or certificate, we still need to fix the configuration a bit. Locate the <issuedTokenParameters> element used and replace it with the first one from the <alternativeIssuedTokenParameters> block using https://identity.thinktecture.com/stsdm/users/issue.svc/mixed/username address.
Running the client application now results in the following error:
SOAP security negotiation with 'https://identity.thinktecture.com/stsdm/users/issue.svc/mixed/username' for target 'https://identity.thinktecture.com/stsdm/users/issue.svc/mixed/username' failed. See inner exception for more details.
This makes sense as re haven’t specified any user credentials for the client yet. Adding the user student with password abc!123 through the client proxy solves this. The client code now looks like:
static void Main(string[] args)
{
try
{
var proxy = new ServiceClient();
proxy.ClientCredentials.UserName.UserName = "student";
proxy.ClientCredentials.UserName.Password = "abc!123";
var data = proxy.GetData(42);
Console.WriteLine(data);
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex.Message);
Console.ResetColor();
}
Console.ReadLine();
}
And now the client can work just fine with the secured service thanks to the StarterSTS hosted by Dominick.
Enjoy!
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu
The WF4 Receive activity shields you from a lot of the WCF pipeline. Normally that is a good thing but there are occasions where you want to know more about the incoming WCF request. Normally you can use the WCF OperationContext.Current to get at this information but with a workflow service this doesn’t work as it is null. The reason is that the workflow executes on a different thread.
So how do you get at the OperationContext?
The trick is to add a class implementing the IReceiveMessageCallback interface to the NativeActivityContext.Properties while the receive is executing and this will have it’s OnReceiveMessage() function executed with the OperationContext passed in. This means creating an activity to wrap the Receive activity like this.
public class GetMessageProperties : NativeActivity
{
public Activity Body { get; set; }
protected override void Execute(NativeActivityContext context)
{
var inspector = new GetMessagePropertiesInspector();
context.Properties.Add(inspector.GetType().FullName, inspector);
context.ScheduleActivity(Body, OnBodyCompleted);
}
private void OnBodyCompleted(NativeActivityContext context, ActivityInstance instance)
{
var inspector = context.Properties.Find(typeof(GetMessagePropertiesInspector).FullName) as GetMessagePropertiesInspector;
if (inspector != null)
{
Console.WriteLine("ClientUri = {0}", inspector.ClientAddress);
}
}
}
The GetMessagePropertiesInspector, which implements the IReceiveMessageCallback, is quite simple. The main thing here is that it can be serialized along with the workflow state so make sure it contains the DataContract attribute.
[DataContract]
public class GetMessagePropertiesInspector : IReceiveMessageCallback
{
[DataMember]
public string ClientAddress { get; set; }
public void OnReceiveMessage(OperationContext operationContext, ExecutionProperties activityExecutionProperties)
{
var properties = operationContext.IncomingMessageProperties;
var endpoint = properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
ClientAddress = endpoint.Address;
}
}
Creating and self hosting the workflow in a console app is easy, if you want to use the designer you will need to add an ActivityDesigner as well and you are good to go.
class Program
{
static void Main(string[] args)
{
var workflow = CreateWorkflow();
var host = new WorkflowServiceHost(workflow, new Uri("http://localhost:8080/MyService"));
host.AddDefaultEndpoints();
host.Open();
Console.ReadLine();
host.Close();
}
private static Activity CreateWorkflow()
{
var result = new Sequence();
var receive = new Receive();
receive.OperationName = "GetData";
receive.CanCreateInstance = true;
result.Activities.Add(new GetMessageProperties() { Body = receive });
result.Activities.Add(new SendReply() { Request = receive });
return result;
}
}
If you want to work with the reply message there is also a ISendMessageCallback which does exactly the same for the result message.
Enjoy!
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu