-->

06/12/2012

Understanding Sharepoint Custom Claims Provider

Before going into any details, we should answer the obvious question.
                                 Why Custom Claims Provider?
Simple ! Claims auth based on Windows based, Forms based or SAML based authentication will generate Claims. But mostly they may not be compatible with the claim information what we / application require.
Below is a claim set generated by STS in case of Claims with Windows.


For example, lets say Date of Birth is part of AD Group, and when user sign in to my site, i will get DOB as one of the claims. But task of my application is to categorize people into Kids (<18 Yrs), Youth (18 to 27), Middle Aged (27 to 40) and so on . . . which will be done by adding one more extra claim with respective values (kids, youth, middle aged . . . ), based on information provided by default claims(Age calculated from DOB). That task will be done by Custom Claim Provider.
Now Custom Claim provider is the layer between Sharepoint STS and My Application, which will act like an adapter.

There are many awesome resources like Articles, Posts and Videos on this specific task. Especially MSDN has a great repository of information. Below is the link.
MSDN Repository

Again Obvious question, then what i will be writing in this post which is different from MSDN?
I have done a very simple example briefing the significance of every pieces of a 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.
  1. FillClaimsForEntity() - Method controls additional claims loaded/created for logged in user
  2. FillHierarchy() - Method controls the Claim provider hierarchy display in search window.
  3. FillSearch() - Method controls the search functionality in all people pickers in site collection.
  4. 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.

Download My Sample Code

Is it helpful for you? Kindly let me know your comments / Questions.

3 comments:

  1. Simple explanation. Thanks!

    ReplyDelete
  2. Can you tell me how can I get this "Youth" and "MiddleAge" claims. will you please help me ?

    ReplyDelete