Condividi tramite


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.

Comments

  • Anonymous
    January 01, 2003
    Thanks for the blog post! Here's a link to the project. code.msdn.microsoft.com/SharePoint-2010-Writing-87dbee1d It would be awesome to have the code bundled as a VS2013 SharePoint 2013 project. That way it's ready to go for debugging and development.

  • Anonymous
    January 01, 2003
    what kind of information is available about the identity and the resource being accessed when the FillClaimsForEntity(Uri context, SPClaim entity, List<SPClaim> claims) method is called? For example, do I get the url and the headers from the httprequest in the SPClaim object that is passed to this method?

  • Anonymous
    January 01, 2003
    Hi Steve. I successfully implemented your example. Then I had to remove Custom Claims Provider I wrote. I done it using Uninstall-SPFeature command.  Custom Claims Provider was removed - I can't see it in the people picker form which is OK for me. But I can't see WingtipSTS and their claim types in the people picker. On the left side of the form I can see only following items:

  • Organizations
  • All Users
  • Active Directory But I should see all items below, because I still use WingtipSTS for my Sharepoint web application:
  • Organizations
  • All Users
  • Active Directory
  • WingtipSTS    EmailAddress    Title Is it possible to set up Sharepoint to have previous (standard) functionality in the people picker form?
  • Anonymous
    January 01, 2003
    The comment has been removed

  • Anonymous
    January 01, 2003
    thanks

  • Anonymous
    January 01, 2003
    Hey Steve, How about sharing your code for the web part that displays the claims?

  • Anonymous
    January 01, 2003
    We thought a Custom Claim Provider would be a great way to add claims to Windows authenticated users in Sharepoint 2010. But the custom Claim Provider is not called only at login time, it is called on every request to the server.  So the custom claim provider is called on every web and for every picture on that web. Since we have a lot of claims to augment, this will be devastating for the SQL server. If I add another authentication provider (FBA) for that zone, it starts augment claims only at login time as it should, even if I login as a Windows user. Is this a known issue and is there a common solution for this? Will it work in Sharepoint 2013?

  • Anonymous
    January 01, 2003
    You Sir, Are my new best friend!  If I could only express in words the headache you helped me avoid.  Thank you much for sharing.

  • Anonymous
    January 01, 2003
    Hi Rick, I posted the code for the part previously at http://blogs.technet.com/speschka/archive/2010/02/13/figuring-out-what-claims-you-have-in-sharepoint-2010.aspx. Steve

  • Anonymous
    June 07, 2010
    Hi,   I am very new to sharepoint development arena. I am working at a company where we are implementing custom claims provider (as compared to windows previously). I am learning the architecture of SP2010 and ADFS v2 and we have AD like stuff which has all the profile and user, groups information. Could somebody help me out in figuring out what are the stuffs that I need to actually start working/implementing custom claims provider people picker SP 2010. Regards Vishwas

  • Anonymous
    June 18, 2010
    Hi Steve any thoughts on social.msdn.microsoft.com/.../6a90bbb0-6100-42e4-8163-a5ec17fdf408 Regards Yogesh Pawar

  • Anonymous
    June 27, 2010
    Hi Nice article I have written a custom STS and membership providers for various applicatrions and have now modified it to work with SP 2010 and have them working. However I am new to Sharepoint programming. I understand after reading the articles and have used them to create a custom claims provider that queries a database for the hierarchy etc. However I have no idea how to create a SP feature for SP2010 can you let me have some info or point me to an article where I can see how his done. Regards Dave I

  • Anonymous
    December 23, 2010
    Great Post, I am trying to access the existing claims PRIOR to augmenting with custom claims.  I can't see to access the users existing claims in the method "FillClaimsForEntity".  Any thoughts?

  • Anonymous
    June 20, 2011
    I just ran this example, worked the first time. Made a change to what claims I wanted to have, deployed, and it's not updating in Sharepoint.  I think it's because the feature activation/deactivation is not uninstalling (?) or updating our SPClaimProvider code in the GAC?  Anyone else have this problem?

  • Anonymous
    December 23, 2013
    Pingback from Writing your own Trusted Identity provider for SP2010 (2) | Stef van Hooijdonk

  • Anonymous
    January 14, 2015
    To anyone using the above example, please see this post:
    http://blogs.technet.com/b/speschka/archive/2012/09/07/important-change-for-custom-claims-providers-in-sharepoint-2013-and-refresh-of-some-favorite-claims-tools.aspx

    When it comes to SharePoint 2013 the above is incomplete without this footnote. Before I learned of it, and in spite of the detailed instructions found here, I had no success with deploying a claims provider.

  • Anonymous
    January 19, 2015
    Although I was able to install the feature via PowerShell as described above there was evidence during development in our logs that a prior version of our custom provider was continuing to run even after removing the feature in order to install a more current version. Eventually, our server failed and had to be completely rebuilt though we haven't determined if it was related to our newly installed claims provider. Upon consultation with Microsoft were were guided to NOT attempt to install SP features by any mechanism other than packaging them in WSPs.

  • Anonymous
    January 22, 2015
    SharePoint low-trust apps and SAML

  • Anonymous
    March 01, 2015
    First let me say that one of the things I like about blogging about random SharePoint mystery meat is