Wednesday, September 29, 2010

Custom Membership Provider and Custom Roles Provider in 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 </system.web> 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