SharePoint 2010 Claims Augmentation

Security is a very important topic for any technology. As a SharePoint 2010 technologist, it is important for you to understand how SharePoint authorization works, and how SharePoint relies on external systems for authentication. With the release of SharePoint 2010, not only can you work under classic windows authentication, you can also leverage claims based authentication. Even within Windows authentication there are nuances of NTLM vs Kerberos.

Augmenting Claims and Registering Your Provider

The first thing that we will do is claims augmentation. As described previously, after a person is authenticated, our claim provider fires. We use the rules that I defined previously to add a claim (which basketball team is their favorite) to the user token.

Use the following steps to write a claim provider. The example uses Microsoft Visual Studio 2010 to create a class library application.


Step 1: Add References

First, add references to Microsoft.SharePoint and Microsoft.IdentityModel. I am assuming that you can find Microsoft.SharePoint.dll in your SharePoint installation. The Microsoft.IdentityModel.dll is installed as part of the Windows Identity Foundation. The following is the full path of Microsoft.IdentityModel.dll in my installation.

\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 have to add our using statements and define the base class that our class inherits from. For this example, we 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 must inherit from the SPClaimProvider base class. The following code shows how our class appears at the start; do not be confused by the name SqlClaimsProvider; I was originally going to write it to work only with the SQL membership and role provider, but later decided to make it work with Windows also.

namespace SqlClaimsProvider
{
    
public class SqlClaims : SPClaimProvider
    {

        
public SqlClaims(string displayName)
            :
base(displayName)
        {
         }
    }
}

Step 3: Adding the Required Implementation
Instead of stepping through all the interfaces that you must implement, you can pause over the SPClaimProvider name in the previous code, click the drop-down arrow that appears under S, and then select the Implement abstract class 'SPClaimProvider' command.

Step 4: Implementing the Functionality Included with the Provider
After you implement the abstract class, you must implement five properties in the SPClaimProvider class, regardless of what you will do: Name, SupportsEntityInformation, SupportsHierarchy, SupportsResolve, and SupportsSearch.
Start with the Supports* properties. If you are doing only claims augmentation, the only one that you must support is entity information. The property list in my class resembles the following.

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, implement support for the Name property. You generally want to support both a display name and an "internal" name by which your provider can be referenced.

In this case, I use internal static properties so that I can share them with another class that I will add later in the project. My implementation for the Name property resembles the following.

internal static string ProviderDisplayName
{
      get
      {
             
return "Basketball Teams";
      }
}

internal static string ProviderInternalName
{
      get
      {
             
return "BasketballTeamProvider";
      }
}

public override string Name
{
      get
      {
             
return ProviderInternalName;
      }
}

We have now finished all the basic implementation. On to more interesting things. First, I set up an array to use in our claims provider for the teams. It is added at the top of the class, as follows.

// Teams that we are using.
private string[] ourTeams = new string[] { "Consolidated Messenger", "Wingtip Toys", "Tailspin Toys " };

Next, we must implement the core of our claims provider. To do claims augmentation, we add implementation code for the FillClaimsForEntity method, the FillClaimTypes method, and the FillClaimValueTypes method in the SPClaimProvider class.

To start, we create some simple helper functions because we will call them throughout the creation of our claims provider. These helper functions define the ClaimType, which is basically our claim namespace or identifier, and the ClaimValueType, such as string or int.

The following are the two helper functions that do that for us.

private static string SqlClaimType
{
      get
      {
             
return "http://schema.steve.local/teams";
      }
}

private static string SqlClaimValueType
{
      get
      {
             
return Microsoft.IdentityModel.Claims.ClaimValueTypes.String;
      }
}

At this point, what you can determine from this is that if our claim provider is working, we will be adding a claim with a name of http://schema.steve.local/teams and the value in that claim is a string.

From the previous code and information, we know that the value will be Consolidated Messenger, Wingtip Toys, or Tailspin Toys. For the claim name itself, there are no required rules for what the name must look like. We usually follow the format schemas.SomeCompanyName.com/ClaimName. In this case, my domain is steve.local. Therefore, I used schemas.steve.local, and teams is the property that I want this claim to track.

The part where we actually add that claim comes next, in the FillClaimsForEntity method of the SPClaimProvider class, as shown in the following code.

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");

// Determine who the user is, so that we know what team to add to their claim
// entity. The value from the input parameter contains the name of the
// authenticated user. For a SQL forms-based authentication user, it looks similar to
// 0#.f|sqlmembership|user1; for a Windows claims user it looks similar
// to 0#.w|steve\\wilmaf.
// I will skip some uninteresting code here, to look at that name and determine
// whether it is a forms-based authentication user or a Windows user, and if it is a forms-based authentication user,
// determine what the number part of the name is that follows "user".

string team = string.
Empty;
int userID = 0;
             
// After the uninteresting code, "userID" will equal -1 if it is a Windows user.
// If it is a forms-based authentication user, it will contain the number that follows "user".
            
// Determine 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 are not one of our forms-based authentication users,
      
// make their favorite team Wingtip Toys.
      
// 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 claims parameter, which is a list of SPClaim objects. We create a new claim by using the CreateClaim helper method.

Finally, when we create the claim, we use the two helper methods that I described previously: the SqlClaimType method and the SqlClaimValueType method.

Now that we have completed the most complex part of this code, there are two additional methods that we must provide an implementation for: FillClaimTypes and FillClaimValueTypes. These methods are easy to do because we are merely going to use our helper methods for them. Their implementation resembles the following.

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 straightforward. Therefore, I will not explain them any more.

Now we have completed our basic implementation of a claims provider, which should do claims augmentation for us. So how do I use it? The preferred method is to use a claims feature receiver.

Implemented custom Sharepoint Custom Claims Provider

Step1:
Download the code from the link. It is the code written by Ted Pattison, a well renowned sharepoint security expert.

Step 2:
The custom provider class should be Implementing abstract class "SPClaimProvider". Understand the declarations part where we declare name of claim provider, its type, Value and the name of the custom claim that we gona induce into claims repository provided by STS.

internal const string ClaimProviderDisplayName = "Audience Claim Provider";
    
internal const string ClaimProviderDescription = "SharePoint 2010 Demoware from Critical Path Training";
    
protected const string AudienceClaimType = "http://www.wingtip.com/identity/claims/audience";
    
protected const string StringTypeClaim = Microsoft.IdentityModel.Claims.ClaimValueTypes.String;

    
public override string Name {
      get {
return ClaimProviderDisplayName; }
    }

Step 3:
Now observe below 4 properties and 2 methods

public override bool SupportsEntityInformation {
      get {
return true; }
    }

    
public override bool SupportsHierarchy {
      get {
return true; }
    }

    
public override bool SupportsResolve {
      get {
return true; }
    }

    
public override bool SupportsSearch {
      get {
return true; }
    }
  
protected override void FillClaimTypes(List claimTypes) {
      
if (claimTypes == null) throw new ArgumentException("claimTypes");

      claimTypes.
Add(AudienceClaimType);
    }

     
protected override void FillClaimValueTypes(List claimValueTypes) {
      
if (claimValueTypes == null) throw new ArgumentException("claimValueTypes");

      claimValueTypes.
Add(StringTypeClaim);
    }

Though they look simple, these properties control the execution of the remaining code. For example, SupportSearch property returns true. This means, when a search happens in my sitecollection, the code in my claim provider will be executed instead of default search code in sharepoint.

Step 4: We have 4 more abstract methods to override.
FillClaimsForEntity() - Method controls additional claims loaded/created for logged in user
FillHierarchy() - Method controls the Claim provider hierarchy display in search window.
FillSearch() - Method controls the search functionality in all people pickers in site collection.
FillResolve() - Method controls functionality executed when you type in a name and click resolve button (one with green tick mark).

Here is where my code sample is simplified for better understanding. Instead of involving audience manager into scope, i simple created a array of strings representing users on my machine.

internal string[] AvailableClaims = {"administrator","spuser","pratap" };

I will add extra claim with claim-type as "http://www.wingtip.com/identity/claims/audience" and Claim-value as their name from this array.

Step 5: Look at the updated methods with simplest possible code.

protected override void FillClaimsForEntity(System.Uri context, SPClaim entity, List claims) {
      
if (entity == null) throw new ArgumentException("entity");
      
if (claims == null) throw new ArgumentException("claims");
          
          string userLogin = (entity.Value.Split('\\'))[1];
    
            foreach(string claim
in AvailableClaims)
             {
                   
if (userLogin.ToLower()==claim.ToLower()) {
                        claims.
Add(CreateClaim(AudienceClaimType, claim, StringTypeClaim));
                    }
             }      
    }

    
protected override void FillHierarchy(System.Uri context, string[] entityTypes, string hierarchyNodeID, int numberOfLevels, SPProviderHierarchyTree hierarchy) {
       
// No additional nodes need to be added to the People Picker
    }

    
protected override void FillSearch(System.Uri context, string[] entityTypes, string searchPattern, string hierarchyNodeID, int maxCount, SPProviderHierarchyTree searchTree) {
       
if (EntityTypesContain(entityTypes, SPClaimEntityTypes.FormsRole)) {
      
List audiences = new List();
    
          foreach (string claim
in AvailableClaims) {
             
if (claim.StartsWith(searchPattern, StringComparison.CurrentCultureIgnoreCase))
                     audiences.
Add(claim);
          }
        
        foreach (string audienceName
in audiences)
          searchTree.AddEntity(CreatePickerEntityForAudience(audienceName));
      }
    }

    
protected override void FillResolve(System.Uri context, string[] entityTypes, string resolveInput, List resolved)
    {
      
List audiences = new List();
        
        foreach (string claim
in AvailableClaims)
        {
            
if (claim.StartsWith(resolveInput, StringComparison.CurrentCultureIgnoreCase))
                 audiences.
Add(claim);
        }

        foreach (string audienceName
in audiences)
        {
             resolved.
Add(CreatePickerEntityForAudience(audienceName));
         }
    }

Output:
Now when i log into application, below are the claims created because of my code

Look at the people picker search window changed from what to what.



If you observe we haven't implemented any thing in FillHierarchy() Method as we didn't have any child nodes to display in our claim provider. But you can implement this method if you want to display some thing like below.



Look how the resolving of names got changed.



If you observe the first one is resolved based on my windows credentials. But the second one is resolved based on my claim value.

Last obvious question, what we achieved from this?
Well let me show you. i have a document which has to be displayed for people whose age>18.
we need to do 2 simple things.

1- Break default inheritance of permissions on that document.
2- Assign the permissions to "Youth" and "MiddleAge" claims.

Now anyone who login with age less than 18 will not be able to see it. Now you don't need to bother about no of people and their ids or their AD Associations. Just follow our custom claim value.

Demo: I have a document library with 3 documents. I want to show only 2 documents to user Pratap.


Go to those two documents and manage their permissions.


Remove all users from access list and remove permission inheritance.



Add access to specific claim type, in this case to "pratap".

Now, when pratap login, this is how the document library will be displayed.



Task accomplished. We have learned about Custom Claims Provider, significance of each method in it and Importantly how granular level security can be implemented using custom claims.

By Ariel Gomez   Popularity  (1811 Views)