Tuesday, September 28, 2010

Custom Membership Provider and Custom Roles Provider for SharePoint 2010



Introduction


Over the past two to three weeks I have struggled and struggled to figure out how the bloody hell to accomplish this task and believe me it has not been an easy task at all!


I found a ton of articles that explained how to use the OOB (out-of-the-box) SqlMembershipProvider but that in my opinion is NOT custom. Custom and out-of-the-box are NOT the same damn thing!


So in this post I will explain to you the full and proper way of creating and implementing a Custom Membership provider as well as a Custom Roles Provider.


Step1: Read up on Claims-based Identity Framework


This will give you a better understanding of how everything is supposed to fit together. I made my first major breakthrough after reading this because the material out there for Claims Based Identity and SharePoint 2010 is very confusing.


Note: Difference between Membership Provider and Claims Augmentation


One of the things that confused me was that some articles that tried to teach you how to configure FBA (Forms Based Authentication) tried to get you to use the OOB SqlMembershipProvider and others tried to get you to write custom claims augmentation. Claims augmentation is only useful if you're trying to extend the Claims/roles provision of an existing membership provider.


It augments the existing security token (in other words it adds additional claims to the existing token).


If you read up about claims-based identity you'll understand that when you log on to SharePoint, the Security Token Service (STS) communicates with your authentication provider and role provider in order to create a security token (which contains one or more claims). That token is then used by the SharePoint web application for purposes of permissions. Important note: Roles are converted to claims.


Step 2: Start the solution


Start a new blank .NET 3.5 solution and add two class libraries to it:


"MyMembershipProvider" and "MyRolesProvider". It is said in some places that you can deploy these classes through Visual Studio 2010 SharePoint 2010 solution template but i could not get it to work.


Step 3: Add references to System.Web and System.configuration for both class libraries.


Step 4: Add using statements for both class files:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration.Claims;
using System.Web.Security;
using Logistics.Utils;


Step 5: Your membership provider must inherit from System.Web.Security.MembershipProvider


Make sure you implement the abstract class. You need to implement at least the following methods in order to have a working Membership Provider:


FindUsersByEmail



public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)


{


/* if the emailToMatch parameter is set to "address@example.com," then users with the e-mail


* addresses "address1@example.com," "address2@example.com," and so on are returned. Wildcard


* support is included based on the data source. Users are returned in alphabetical order by


* user name.The results returned by FindUsersByEmail are constrained by the pageIndex and


* pageSize parameters. The pageSize parameter identifies the number of MembershipUser objects


* to return in the MembershipUserCollection collection. The pageIndex parameter identifies


* which page of results to return, where 1 identifies the first page. The totalRecords


* parameter is an out parameter that is set to the total number of membership users that


* matched the emailToMatch value. For example, if 13 users were found where emailToMatch


* matched part of or the entire user name, and the pageIndex value was 2 with a pageSize of 5,


* then the MembershipUserCollection would contain the sixth through the tenth users returned.


* totalRecords would be set to 13.


* */



MembershipUserCollection muc = new MembershipUserCollection();


totalRecords = 0;


try


{


if (emailToMatch.ToLower() == "myaddy@gmail.com")


{


muc.Add(new MembershipUser(


this.ApplicationName,


"rttjonty",


0,


"myaddy@gmail.com",


"",


"",


true,


false,


DateTime.Now,


DateTime.Now,


DateTime.Now,


DateTime.Now,


DateTime.Now));



totalRecords = 1;


}


}


catch (Exception ex)


{



throw;



}



return muc;


}


FindUsersByName


public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)


{


/* For example, if the usernameToMatch parameter is set to "user," then the users "user1,"


* "user2," "user3," and so on are returned. Wildcard support is included based on the data


* source. Users are returned in alphabetical order by user name.The results returned by


* FindUsersByName are constrained by the pageIndex and pageSize parameters. The pageSize


* parameter identifies the number of MembershipUser objects to return in the


* MembershipUserCollection. The pageIndex parameter identifies which page of results to return,


* where 1 identifies the first page. The totalRecords parameter is an out parameter that is set


* to the total number of membership users that matched the usernameToMatch value. For example,


* if 13 users were found where usernameToMatch matched part of or the entire user name, and the


* pageIndex value was 2 with a pageSize of 5, then the MembershipUserCollection would contain


* the sixth through the tenth users returned. totalRecords would be set to 13.


* */



MembershipUserCollection muc = new MembershipUserCollection();


totalRecords = 0;



try


{


if (usernameToMatch.ToLower() == "rttjonty")


{


muc.Add(new MembershipUser(


this.ApplicationName,


"rttjonty",


0,


"myaddy@gmail.com",


"",


"",


true,


false,


DateTime.Now,


DateTime.Now,


DateTime.Now,


DateTime.Now,


DateTime.Now));



totalRecords = 1;


}


}


catch (Exception ex)


{


throw;


}



return muc;


}


GetUser(string, bool)


public override MembershipUser GetUser(string username, bool userIsOnline)


{


/* Dont know what the userIsOnline value is supposed to do */


MembershipUser usr = null;



try


{


if (username.ToLower() == "rttjonty")


{


usr = new MembershipUser(


this.ApplicationName,


"rttjonty",


0,


"myaddy@gmail.com",


"",


"",


true,


false,


DateTime.Now,


DateTime.Now,


DateTime.Now,


DateTime.Now,


DateTime.Now);


}


}


catch (Exception ex)


{



throw;


}



return usr;


}


GetUser(object, bool)


public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)


{


/* Dont know what the userIsOnline value is supposed to do */


MembershipUser usr = null;



try


{


if ((int)providerUserKey == 0)


{


usr = new MembershipUser(


this.ApplicationName,


"rttjonty",


0,


"myaddy@gmail.com",


"",


"",


true,


false,


DateTime.Now,


DateTime.Now,


DateTime.Now,


DateTime.Now,


DateTime.Now);


}


}


catch (Exception ex)


{



throw;


}



return usr;


}



GetUserNameByEmail


public override string GetUserNameByEmail(string email)


{


try


{


if (email.ToLower() == "myaddy@gmail.com")


{


return "rttjonty";


}


}


catch (Exception ex)


{



throw;


}


return "";


}


ValidateUser


public override bool ValidateUser(string username, string password)


{


try


{


if (username == "RTTJONTY" && password == "P@ssw0rd")


{


return true;


}


}


catch (Exception ex)


{



throw;


}


return false;


}



Step 6: Your role provider must inherit from System.Web.Security.RoleProvider. Implement the inheritance and implement at least the following methods in order to have a functional Role Provider:


GetRolesForUser


public override string[] GetRolesForUser(string username)


{


try


{


if (username.ToLower() == "rttjonty")


{


string[] roles = new string[] { "KingOfTheHill", "MasterOfTheUniverse" };


return roles;


}


}


catch (Exception ex)


{



throw;


}



return new string[]{};


}


RoleExists


public override bool RoleExists(string roleName)


{


try


{


switch (roleName)


{


case "KingOfTheHill":


return true;


case "MasterOfTheUniverse":


return true;


}


}


catch (Exception ex)


{



throw;


}


return false;


}



Step 7: Sign both assemblies with strong name keys


Step 8: Build your class libraries and copy the DLLs to your GAC. (C:\Windows\Assembly)


Step 9: Register the DLLs in the web.config file for STS.


The web.config file for the STS can be found at:


C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\WebServices\SecurityToken


Remember: To make backups of all web.config files that you change.


<system.web>


<roleManager defaultProvider="c" enabled="true" cacheRolesInCookie="false">


<providers>


<add name="c" type="Microsoft.SharePoint.Administration.Claims.SPClaimsAuthRoleProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />


<add name="RTTOracleRoleProvider" type="RTTOracleRoleProvider.RTTOracleRoleProvider,RTTOracleRoleProvider, Version=1.0.0.1, Culture=neutral, PublicKeyToken=d7a20552fd6361ca"/>


providers>


roleManager>


<membership defaultProvider="i">


<providers>


<add name="i" type="Microsoft.SharePoint.Administration.Claims.SPClaimsAuthMembershipProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />


<add name="RTTOracleMembershipProvider" type="RTTOracleMembershipProvider.RTTOracleMembershipProvider, RTTOracleMembershipProvider, Version=1.0.0.1, Culture=neutral, PublicKeyToken=e80a76e512200597"/>


providers>


membership>


system.web>


* Note: In my scenario I’ve named my membership provider “RTTOracleMembershipProvider” and my Roles provider is called “RTTOracleRoleProvider” but yours will be different. Also note that in this example I’m using Version=1.0.0.1; yours will probably be 1.0.0.0 or whatever you’ve set your assembly version to.


* * Note: It is important to remember to include the default providers (SPClaimsAuthRoleProvider and SPClaimsAuthMembershipProvider). Mine didn’t work until I set these as default providers.


Step 10: Register the DLLs in the web.config file for Central Admin


This one can be found at:


C:\inetpub\wwwroot\wss\VirtualDirectories\3218 (remember to replace 3218 with the port number for your Central Admin)


<roleManager>


<providers/>


roleManager>


<membership defaultProvider="i">


<providers>


<add name="i" type="Microsoft.SharePoint.Administration.Claims.SPClaimsAuthMembershipProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />


<add name="RTTOracleMembershipProvider" type="RTTOracleMembershipProvider.RTTOracleMembershipProvider, RTTOracleMembershipProvider, Version=1.0.0.1, Culture=neutral, PublicKeyToken=e80a76e512200597"/>


providers>


membership>


system.web>


In this sample the node does not have a matching opening node because its only the tail-end of the node just to show you where in the web.config you need to register.


In the central admin you should leave the roleManager section empty, for some reason if you register the Role Provider here it cause an ArgumentException.


Step 11: Create your web application


Go to Central Admin / Application Management and create your new application. Select Claims-Based Authentication and tick Forms-Based further down the form. You can have both Windows Authentication AND Forms Based going at the same time. When you attempt a login, the application will ask you if you want to log in with Forms authentication or Windows authentication.


In the FBA section you will see two textboxes where you can specify your newly created Membership Provider and Role Provider. Just enter their class names, not the assembly names. (i.e. enter “MyMembershipProvider” and not “MyMembershipProvider.dll”. This corresponds to the Name attribute you registered your DLLs with.)


** Note: If you’ve done everything correctly up until now you should be able to use your new Membership provider to specify site owners.


If you want to use your custom providers with an existing SharePoint web application then you’ll have to extend that application to include FBA.



Step 12: Register the DLLs in the web.config for your newly created web application


<roleManager defaultProvider="c" enabled="true" cacheRolesInCookie="false">


<providers>


<add name="c" type="Microsoft.SharePoint.Administration.Claims.SPClaimsAuthRoleProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />


<add name="RTTOracleRoleProvider" type="RTTOracleRoleProvider.RTTOracleRoleProvider,RTTOracleRoleProvider, Version=1.0.0.1, Culture=neutral, PublicKeyToken=d7a20552fd6361ca"/>


providers>


roleManager>


<membership defaultProvider="i">


<providers>


<add name="i" type="Microsoft.SharePoint.Administration.Claims.SPClaimsAuthMembershipProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />


<add name="RTTOracleMembershipProvider" type="RTTOracleMembershipProvider.RTTOracleMembershipProvider, RTTOracleMembershipProvider, Version=1.0.0.1, Culture=neutral, PublicKeyToken=e80a76e512200597"/>


providers>


membership>


system.web>


Found at:


C:\inetpub\wwwroot\wss\VirtualDirectories\16771 (remember to replace 3218 with the port number for your newly created web application)


Note: Remember to include default providers as in above example
Note: Remember to include the correct version


Step 13: Enjoy!


That’s it folks, you should now be able to log in as the owner (whether you specified FBA owner or windows auth owner) and set up permissions for your new application using either FBA or Windows Auth. You should even be able to set up permissions for roles. Have fun and play around!



No comments:

Post a Comment