In the previous post I introduced a scenario where you can use .NET Services Access Control and Windows LiveID to delegate authentication and authorization. In this post we will go through the different pieces needed in the application to perform authorization checks. First thing will be configure the passive federation using Geneva on the application and later we will create an ASP.NET MVC action filter to perform the access check against the incoming claims.

Note: all the code showed here is using Microsoft Identity Framework "Zermatt" Beta 1. The new Geneva Framework might have some changes.

Configuring passive federation on the website

Configure passive federation on the website is about defining which SAML token version we will accept and the certificate we will use to decrypt the incoming token. The following configuration uses Zermatt Beta 1, so this probably changes on Geneva.

<microsoft.identityModel>
    <tokenHandlers>
      <remove type="Microsoft.IdentityModel.Tokens.Saml11.Saml11TokenHandler, Microsoft.IdentityModel, Version=0.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      <add type="Microsoft.IdentityModel.Tokens.Saml11.Saml11TokenHandler, Microsoft.IdentityModel, Version=0.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <samlSecurityTokenRequirement>
          <allowedAudienceUris>
            <add value="http://localhost/YourApp/" />
          </allowedAudienceUris>
        </samlSecurityTokenRequirement>
      </add>
    </tokenHandlers>
    <federatedAuthentication enabled="true">
    </federatedAuthentication>
    <serviceCertificate>
      <certificateReference findValue="01 20 …" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" />
    </serviceCertificate>
  </microsoft.identityModel>

When the user click on the sign in button, the link will point to to the .NET Services Access Control passive STS url. The following method uses Geneva to generate this WS-Federation url.

private static string GetFederationUrl(string realm, string issuer, string homeRealm, string returnUrl)
{
    FederatedAuthenticationModule fam = new FederatedAuthenticationModule();
    fam.Realm = realm;
    fam.Issuer = issuer;
    fam.Reply = returnUrl;
    SignInRequestMessage signInMsg = fam.CreateSignInRequest();
    signInMsg.Parameters.Add("whr", homeRealm);
    string url = signInMsg.WriteQueryString();
    return url;
}

The following code and configuration will give you an idea of the url that is being built. Pay attention to this url because a small change might break the whole thing.

string url = GetFederationUrl(ConfigurationManager.AppSettings["AccessControlRealm"],
                            ConfigurationManager.AppSettings["AccessControlIssuer"],
                            ConfigurationManager.AppSettings["AccessControlHomeRealm"],
                            replyTo);
<!– Windows Azure Federation –>
<add key="AccessControlRealm" value="http://localhost/YourApp/"/> <!– should match to a scope –>
<add key="AccessControlIssuer" value="https://accesscontrol.windows.net/passivests/yoursolution/LiveFederation.aspx"/>
<add key="AccessControlDefaultReply" value="http://localhost/YourApp" />
<add key="AccessControlHomeRealm" value="http://login.live.com" />

The AccessControlRealm config is important because it will match the scope on your solution. You will have to configure the scope to encrypt with the public key of your website certificate and create the claim mappings from Windows LiveID to your well known claims. If you don’t have the scope created or configured to output at least one claim you will get a 403 Forbidden on the .NET Services Access Control STS.

image

Performing access check in the web site

Now that we have everything configured and the token should be coming back to our website, it’s time to do the access check. By using Geneva, the token will be transformed to a Principal object and it will be accessed through the ClaimsPrincipal static class. On the other hand, ASP.NET MVC allow us to plug into the action execution pipeline and get access to the context data like route values. The following code shows an ActionFilterAttribute that will grab the claims from the the Geneva ClaimsPrincipal and will call a strategy class that will perform the access check. If the access check is not successful, the filter will render a NotAuthorized view.

namespace YourApp.Identity
{
    using System;
    …

    public class ClaimAuthorizationRouteFilterAttribute : ActionFilterAttribute
    {
        public ClaimAuthorizationRouteFilterAttribute(string[] operations)
        {
            this.Operations = operations;
        }

        public string[] Operations { get; set; }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var identity = ClaimsPrincipal.Current.Identity as IClaimsIdentity;
            var claims = identity.Claims.ToArray();
            var routeData = context.RouteData.Values.ToArray();
            var strategy = CreateAuthorizationStrategy();
            var executionContext = new ExecutionContext()
            {
                ClaimsNeeded = Operations,
                OperationContextData = routeData,
            };

            if (!strategy.IsAuthorizedFor(executionContext, claims))
            {
                context.Result = new ViewResult
                {
                    ViewName = "NotAuthorized"
                };
            }

            base.OnActionExecuting(context);
        }

    }
}

Finally, the following code shows an implemented strategy for a multi tenant application that manage projects.

namespace YourApp.Identity
{
    using System.Linq;
    using System;

    public class StandardAuthorizationStrategy : IAuthorizationStrategy
    {
        private const string ProjectClaimType = "urn:Project";
        private const string TenantClaimType = "urn:Tenant";
        private const string OperationClaimType = "urn:Operation";

        public bool IsAuthorizedFor(ExecutionContext context, Microsoft.IdentityModel.Claims.Claim[] claims)
        {
            bool authorized = true;
            var tenantClaim = claims.SingleOrDefault(c => c.ClaimType == TenantClaimType);
            var operationClaims = claims.Where(c => c.ClaimType == OperationClaimType);
            var projectClaims = claims.Where(c => c.ClaimType == ProjectClaimType);
        var tenant = context.OperationContextData["Tenant"].ToString();
        var project = context.OperationContextData["Project"].ToString();

            if (!string.IsNullOrEmpty(tenant))
            {
                authorized &= tenantClaim.Value.Equals("*", StringComparison.OrdinalIgnoreCase) ||
                              tenantClaim.Value.Equals(tenant, StringComparison.OrdinalIgnoreCase);
            }

            if (!string.IsNullOrEmpty(project))
            {
                authorized &= projectClaims.Where( p => p.Value.Equals("*", StringComparison.OrdinalIgnoreCase)).Count() > 0 ||
                              projectClaims.Where( p => p.Value.Equals(project, StringComparison.OrdinalIgnoreCase)).Count() > 0;
            }

            if (context.Operations != null)
            {
                bool temp = true;
                foreach (string op in context.ClaimsNeeded)
                {
                    temp &= operationClaims.Where(o => o.Value.Equals(op, StringComparison.OrdinalIgnoreCase)).Count() > 0 ||
                            operationClaims.Where(o => o.Value.Equals("*", StringComparison.OrdinalIgnoreCase)).Count() > 0;
                }
                authorized &= temp;
            }

            return authorized;
        }
    }
}

The only thing left is to put an attribute above the action. The following attribute specifies that the New action will be executed if the incoming token contains the following "urn:Operation" claims.

public class ProjectsController : Controller
{

        [ClaimAuthorizationRouteFilter(new string[] {
                                            "AddUser",
                                            "AddUsersToProject",
                                            "CreateProject" })]
        public ActionResult New() {
        ….

    }

    …

}

So if a user browses to: http://yourapp/Contoso/Projecsts/New, the filter will call the strategy that will check:

  • if the user contains a tenant claim with the value "Contoso" (taken from the route data)
  • if the user contains three operation claims: AddUser, AddUsersToProject and CreateProject

And if a user browses to: http://yourapp/Contoso/Projecsts/some-project/Edit, the filter will call the strategy that will check:

  • if the user contains a "tenant" claim with the value "Contoso" (taken from the route data)
  • if the user contains the "operation" claims specified in the Edit action
  • if the user contains a "project" claim with the value "some-project"

Azure Services Kit available

October 30th, 2008

PA280103_50percent

The work we did through the last couple of months is materialized now. James Conard and the DPE team (Nigel Watling, Ryan Dunn, Vittorio Bertocci, Drew Robbins, et al) were able to hit the road before anyone else at Microsoft by releasing the Azure Services Training Kit.

I was walking through the Hands On Labs lounge today, watching at people doing lines to do the labs.

The kit includes labs on:

  • SQL Data Services
  • Windows Azure
  • .NET Services Service Bus
  • .NET Services Access Control
  • .NET Services Workflow

You will need an invitation code, but you will be able to read the document and code to get lots of information.

Download it now

Thesis - Software as a Service

September 28th, 2008

cloud computing

My interest in Software as a Service (SaaS) born during a trip to Microsoft in 2006. Looking for an interesting topic to elaborate on my graduate thesis, I started digging on different areas and asking different colleagues and friends. Initially, influenced by the work of Arvindra Sehmi, I got interested in agent programming (BDI agents, multi-agents systems, etc). Later, Eugenio Pace, a mentor and friend, commented me about an emerging model of software distribution. After a couple of months, Alejandro Jack (mentor and friend also), contacted me with John DeVadoos (former director of the Architecture Strategy Team at Microsoft, now leading the patterns & practices team). This group was formed by Gianpaolo Carraro and Fred Chong initially and last year Eugenio joined them and Fred left. Their daily job consists of researching the Software as a Service model from the architectural point of view. As part of this challenge, the group establishes relationships with clients interested in the model and with product teams looking for feedback to shorten the gap Redmond Northwind Hosting 042on the platform. The group also publishes a number of papers and proof of concepts using Microsoft technologies. I did a good connection with them and finally decided to pick Software as a Service as the topic for my thesis.

Throughout the last two years I’ve been collaborating with this group writing proof of concepts, preparing and delivering workshops. On the right, it’s me working with Eugenio (on the left) at building 20 one year ago (Gianpaolo is taking the picture). The post-its on the wall are the user stories for Northwind Hosting (I was happy to see Google App Engine six months later as a validation of our thinking).

I’ve witnessed the growth of Software as a Service since it was in the initial stages up to now that has started being adopted by the industry and lately has been extended to a broader term: Cloud Computing.

The thesis is the sum of all the experience I gather along these years and it’s an attempt to summarize and compress the taxonomy of Software as a Service applications on a single model; that is more about “breadth” than “depth”. This model was based on Feature Modeling, a technique used in Software Product Lines (SPL). Feature Modeling is a method and notation to elicit and represent common and variable features of the system in a system family. It was first proposed by Kang et al in Feature Oriented Domain Analysis by the Software Engineering Institute (SEI, 90). It’s been used lately in Generative Software Development (Czarnecki, 2005), which aims at modeling and implementing system families in such a way that a given system can be automatically generated from specification written in one or more textual or graphical domain specific languages (DSLs). Since SaaS and Cloud Computing are evolving fast I wanted to separate the problem space from the solution space allowing the individual development and growth of each of them. Feature Modeling helped because it was focused on the capabilities. I didn’t try to use Feature Modeling to automate the generation of this kind of systems, though. The priority was having a model that allow me to frame and explain Software as a Service systems.

 

image

Separation between problem and solution space (Overview of Generative Software Development, Czarnecki)

Part of defining the problem space consisted of doing a domain analysis. This is an activity of SPL aiming to characterize a domain by understanding their commonality and variability. If you follow this blog you might have read the cloud computing taxonomy map post. That was an exercise during the domain analysis that helped me understand the different scopes, offerings and features of Software as a Service.

image

Domain analysis of Software as a Service

With this information, I’ve spent a couple of days pasting post-its on the wall, trying to group the features and capabilities in a way that makes sense. The end result was this “onion” diagram where each category holds the different features of Software as a Service.

image

The model is later refined, from the taxonomy above to the capability layer (problem space) and finally to the implementation layer (solution space) as shown in the following figure.

image

The thesis then describes each of the capabilities in a high level fashion and then proposes patterns, architecture styles and technologies to implement those capabilities.

The following feature tree is an instance of the capability layer of the model for the LitwareHr application (grayed capabilities are not part of LitwareHr system). This content is in Spanish, but it will be soon available in English.

image 

The thesis also includes other non technical aspects like

  • Adoption and diffusion analysis of SaaS based on market research
  • Barriers for adoption
  • Historical context (starting from specialization in the 19th century, passing through outsourcing, mainframes and what not :)
  • Roles and ecosystem

I want to thanks again to all the people that helped directly or indirectly: my family and fiancee, Alejandro Jack, Gustavo López, Eugenio Pace, Gianpaolo Carraro, Fred Chong, Arvindra Sehmi, Ariel Schapiro, Angel “Java” Lopez and to all Southworks.

Fell free to download the Spanish version and let me know if you find it useful to matias at southworks dot net.

Lately the term SaaS became a broader term and now it is called Cloud Computing (see David Chappell’s paper and Wikipedia). It includes the whole paradigm of utility computing + saas + platform as a service + * as a service.

I’ve got good feedback on the taxonomy map from the blogsphere (including Jeff Kaplan, from THINK IT Services). I updated the map some time ago but didn’t have time to publish. So here it is rather sooner than later. (I need Pablo’s help to do the animated GIF, so this time is static)

 

Cloud Taxonomy Map Features

 

Cloud Taxonomy Map Services