Writing a Custom Claims Provider for SharePoint 2010 - Part 1: Claims Augmentation and Registering Your Provider

This is the first in a series of blogs in which I'll talk about writing a custom claims provider. First, it's probably worth understanding a little background on what a claims provider is and why we might want to use one. A claims provider in SharePoint 2010 is primarily used for two reasons - 1) to do claims augmentation and 2) to provide name resolution.

Claims augmentation is part of a process that occurs after you log onto a claims authentication site. It's important to remember that everything can run with claims authentication in SharePoint 2010 - Windows claims (when you log in with NTLM or Kerberos), FBA claims (when you use an ASP.NET Membership and Role provider), or SAML claims (when you login via an STS like ADFS v2, formerly known as Geneva Server). After you've logged in, all the registered claims providers for that web application fire. When they do, they can add additional claims that weren't provided at login time. For example, maybe you logged in with using Windows authentication, but we want to grant an additional claim that can be used for accessing an SAP system.

Name resolution really comes in two parts - people picker and type-in control. For the people picker, when you want people to be able to search for and find your claims, a claims provider allows you to do this. The type-in control is where you can type in a user name, or a claim, and click the resolve button. Your claims provider can look at that input and determine if it's valid - if so then it will "do the right thing" to resolve it.

So now that we've talked about this in rather abstract terms, let's lay out the scenario we're going to use as we build this custom claims provider. For our particular scenario, we want to assign rights in our site collection based on a person's favorite basketball team. This is one of the really great features about claims authentication that gets some getting used to - we don't really care who they are, we don't really care how they authenticated to the site, all we care about is an attribute of them - what's their favorite team.

QUICK NOTE: I'm using a later build than the public beta of SharePoint 2010. The samples shown in this series are not expected to work with the public beta version.

For our web application, I've enabled both Windows claims and FBA claims. I'm using the standard SQL membership and role provider for the FBA users, and I've pre-populated my list of FBA accounts with user1 through user50. To simplify the scenario, a person's favorite team will be determined in this way:

· All Windows users' favorite team is "Blazers"

· For FBA users:

o user1 through user15 have a favorite team of "Blazers"

o user16 through user30 have a favorite team of "DVK Jovenut"

o user31 through user50 have a favorite team of "Shanghai Tigers"

Claims Augmentation

The first thing we're going to do is claims augmentation. As described above, once a person has authenticated, our claim provider is going to fire. We'll use the rules I've defined above then to add a claim with their favorite basketball team. So we start out in Visual Studio and create a new Class Library application.

Step 1 - Add References

The first thing we want to do is to add references to Microsoft. SharePoint and Microsoft.IdentityModel. I'm assuming you all should be able to find Microsoft.SharePoint. Microsoft.IdentityModel is installed as part of the Windows Identity Foundation. In my particular installation I found this assembly at C:\Program Files\Reference Assemblies\Microsoft\Windows Identity Foundation\v3.5\Microsoft.IdentityModel.dll.

Step 2 - Add Using Statements and Class Inheritance

Now that we have our references, we need to add our using statements and define the base class that our class inherits from. For this example we are going to use the following using statements:

 using Microsoft.SharePoint.Diagnostics;

using Microsoft.SharePoint;

using Microsoft.SharePoint.Administration;

using Microsoft.SharePoint.Administration.Claims;

using Microsoft.SharePoint.WebControls;

 

Our class itself needs to inherit from the SPClaimProvider base class. So here's what our class looks like at the start; don't be thrown by the name - I was originally only going to write it to work with the SQL membership and role provider but later decided to make it work with Windows too:

namespace SqlClaimsProvider

{

    public class SqlClaims : SPClaimProvider

    {

 

        public SqlClaims(string displayName)

  : base(displayName)

        {

        }

    }

}

 

Step 3 - Add Required Implementation

Rather than go through step-by-step all of the interfaces you need to implement, instead let me just suggest that you hover over the SPClaimProvider name above, click the drop down that appears under the S, and select the Implement abstract class 'SPClaimProvider' menu option.

Step 4 - Implement the Functionality Included with the Provider

Once you implement the abstract class, there are five properties we need to implement no matter what we're going to do: Name, SupportsEntityInformation, SupportsHierarchy, SupportsResolve, and SupportsSearch. We'll start with the "Supports..." properties first. If you're only doing claims augmentation, then the only one you really need to support is entity information. So the property list in my class looks like this:

public override bool SupportsEntityInformation

{

      get

      {

            return true;

      }

}

 

public override bool SupportsHierarchy

{

      get

      {

            return false;

      }

}

 

public override bool SupportsResolve

{

      get

      {

            return false;

      }

}

 

public override bool SupportsSearch

{

      get

      {

            return false;

      }

}

 

Next we'll implement support for the Name property. You will generally want to support both a display name and an "internal" name by which your provider can be referenced. In this case I'm going to use internal static properties so that I can share it with another class I'll be adding later in the project. So here's what my implementation for Name looks like:

internal static string ProviderDisplayName

{

      get

      {

            return "Basketball Teams";

      }

}

 

 

internal static string ProviderInternalName

{

      get

      {

            return "BasketballTeamProvider";

      }

}

 

 

public override string Name

{

      get

      {

            return ProviderInternalName;

      }

}

 

Okay, we've gotten all the basic and fairly uninteresting goo out of the way now. On to more interesting things. First, I've set up an array that we'll use in our provider for the teams; it's been added at the top of the class:

//teams we're using

private string[] ourTeams = new string[] { "Blazers", "DVK Jovenut", "Shanghai Tigers" };

 

Now we need to implement the guts of our provider. To do claims augmentation we want to add implementation code for FillClaimsForEntity, FillClaimTypes, and FillClaimValueTypes. To start with, we'll create a couple of simple helper functions because we'll be calling upon them throughout the creation of our provider. Those helper functions are to define the ClaimType, which is basically our claim namespace or identifier, and the ClaimValueType, like string, int, etc. Here are the two helper functions that do that for us:

private static string SqlClaimType

{

      get

      {

            return "https://schema.steve.local/teams";

      }

}

 

private static string SqlClaimValueType

{

      get

      {

            return Microsoft.IdentityModel.Claims.ClaimValueTypes.String;

      }

}

 

So what you can tell from this so far is that if our claim provider is working, we will be adding a claim with a name of https://schema.steve.local/teams and the value in that claim will be a string.  From the preceding code and information in this post, we know that the value will be Blazers, DVK Jovenut, or Shanghai Tigers.  As far as the claim name itself, there aren't hard and fast rules for what the name has to look like.  In general, we usually go "schemas.company.com/claimname".  In this case my domain is "steve.local", so I used "schemas.steve.local", and "teams" is the property I want this claim to track.

 The part where we actually add that claim comes next - in the FillClaimsForEntity method. Let's look at the code for that:

protected override void FillClaimsForEntity(Uri context, SPClaim entity,

      List<SPClaim> claims)

{

if (entity == null)

      throw new ArgumentNullException("entity");

 

if (claims == null)

      throw new ArgumentNullException("claims");

 

//figure out who the user is so we know what team to add to their claim

//the entity.Value from the input parameter contains the name of the

//authenticated user. for a SQL FBA user, it looks something like

// 0#.f|sqlmembership|user1; for a Windows claims user it looks something

//like 0#.w|steve\\wilmaf

//I'll skip some boring code here to look at that name and figure out

//if it's an FBA user or Windows user, and if it's an FBA user figure

//out what the number part of the name is after "user"

 

string team = string.Empty;

int userID = 0;

           

//after the boring code, "userID" will equal -1 if it's a Windows user,

//or if it's an FBA user then it will contain the number after "user"

           

//figure out what the user's favorite team is

if (userID > 0)

{

      //plug in the appropriate team

      if (userID > 30)

            team = ourTeams[2];

      else if (userID > 15)

            team = ourTeams[1];

      else

            team = ourTeams[0];

}

else

      team = ourTeams[1];

      //if they're not one of our FBA users then make their favorite team DVK

 

//add the claim

claims.Add(CreateClaim(SqlClaimType, team, SqlClaimValueType));

}

 

The main thing worth pointing out from that method is the very last line of code. We take the input parameter "claims", which is a list of SPClaim objects. We create a new claim using the CreateClaim helper method. I strongly encourage you to use the helper methods whenever possible. They tend to do a few additional things more than just using the default constructors and you will generally find things to work more completely when you use them. Finally, when we create the claim we use the two helper methods that I described earlier - the SqlClaimType and SqlClaimValueType methods.

Okay, we've really done the most complicated part of this code now, but there's two additional methods we need to provide an implementation for: FillClaimTypes and FillClaimValueTypes. These are actually going to be pretty simple to do because we're just going to use our helper methods for them. Here's what their implementation looks like:

protected override void FillClaimTypes(List<string> claimTypes)

{

      if (claimTypes == null)

            throw new ArgumentNullException("claimTypes");

 

      //add our claim type

      claimTypes.Add(SqlClaimType);

}

 

protected override void FillClaimValueTypes(List<string> claimValueTypes)

{

      if (claimValueTypes == null)

            throw new ArgumentNullException("claimValueTypes");

 

      //add our claim value type

      claimValueTypes.Add(SqlClaimValueType);

}

 

I think those should be pretty straightforward so I'm won't dig into them any further. So now we have our basic implementation of a claims provider done, which should do claims augmentation for us. So how do I use it? Well the preferred method is to use a claims feature receiver. To be blunt, it's the only way I know how to get it registered and working right now in fact so that's what I'll explain how to do.

Step 5 - Create the Provider Feature Receiver

For this step we'll start by adding a new class to the project. It's going to inherit from the SPClaimProviderFeatureReceiver base class. The implementation of it is really pretty straightforward so I'll just paste all the code in here and then briefly walk-through it.

using Microsoft.SharePoint;

using Microsoft.SharePoint.Administration;

using Microsoft.SharePoint.Administration.Claims;

using Microsoft.SharePoint.Diagnostics;

 

 

namespace SqlClaimsProvider

{

    public class SqlClaimsReceiver : SPClaimProviderFeatureReceiver

    {

 

        private void

            ExecBaseFeatureActivated(

            Microsoft.SharePoint.SPFeatureReceiverProperties properties)

        {

            //wrapper function for base FeatureActivated. Used because base

            //keywork can lead to unverifiable code inside lambda expression

            base.FeatureActivated(properties);

        }

 

        public override string ClaimProviderAssembly

        {

            get

            {

                return typeof(SqlClaims).Assembly.FullName;

            }

        }

 

        public override string ClaimProviderDescription

        {

            get

            {

                return "A sample provider written by speschka";

            }

        }

 

        public override string ClaimProviderDisplayName

        {

            get

            {

            //this is where we reuse that internal static property

                return SqlClaims.ProviderDisplayName;

            }

        }

 

        public override string ClaimProviderType

        {

            get

            {

                return typeof(SqlClaims).FullName;

            }

        }

 

        public override void FeatureActivated(

            SPFeatureReceiverProperties properties)

        {

            ExecBaseFeatureActivated(properties);

        }

    }

}

 

Really the only things worth pointing out I think are:

· ClaimProviderAssembly and ClaimProviderType are just using type properties of the custom provider class that we wrote

· ClaimProviderDisplayName uses the internal static property that we created on our claims provider class. That's why we did that - just so we could reuse it in our claims feature receiver

· The FeatureActivated event calls our special internal method for activating properties

Step 6 - Compile Assembly and Add to Global Assembly Cache

This step I'm putting in as a manual step, but you could obviously do it with a solution package if you wanted to go that way. In this scenario I'm just doing everything all on one server while I'm in development mode so I'm doing it a little differently than if I were ready to distribute it to my farm. So for now, make sure your assembly is strongly-named and then compile it. To move things along, use whatever your method of choice is (I just use a post-build event and gacutil) to add the assembly to the Global Assembly Cache, or GAC. Now we're ready to create and deploy our feature.

Step 7 - Create and Deploy Claims Provider Feature

For this step there isn't anything specific to claims providers. We're just going to create a standard SharePoint feature and deploy it. The basics of doing that are kind of outside the scope of this posting, so let me just paste the Xml from my feature.xml file in here so you can see what it looks like:

<Feature   Id="3E864B5C-D855-43e4-B41A-6E054C6C0352"

           Title="Sql Claims Provider Feature"

           Scope="Farm"

           ReceiverAssembly="SqlClaimsProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=eaf1ba21464322ae"

           ReceiverClass="SqlClaimsProvider.SqlClaimsReceiver"

           xmlns="https://schemas.microsoft.com/sharepoint/" />

With my feature.xml file in hand, I created a directory called SqlClaimsProvider in my Features folder and installed my feature; since it's a Farm-scoped feature then it was automatically activated. Here's the command to do that in PowerShell:

install-spfeature -path SqlClaimsProvider

Okay, all of the pieces are in place now - we're ready to try it out.

Step 8 - Log Into the Site and Try It Out

For this final step I'm using my web application that I described at the beginning - it supports Windows claims and FBA claims. To validate whether my claims are being property augmented, I have created a web part that displays all of my claims. I'll show you screenshots of that to validate that the claims augmentation is actually working. First, I log into the site using Windows credentials. Remember from our code above that a Windows user should always be assigned "DVK Jovenut" as his or her favorite team. So here's what it looks like for the Windows user:

And here's what it looks like for an FBA user:

Okay, this is cool stuff - we've got our claims augmentation working. In the next posting, I'll show how to start working with the people picker. We'll do a shorter post where we talk about supporting and adding a hierarchy to the people picker.