Tuesday, 19 December 2006

Implementing a custom role provider

This is not for the faint hearted, but it is quite important – every developer should be able to understand most of this, if you want me to explain anything in further detail then let me know and Ill be happy to help!

Brief:
To produce a system which uses active directory (AD) which will allow access to pages on a temporary basis (say two weeks) without adding the user into a role within AD

Prior knowledge required:
An understanding of site maps and security trimming (e.g. remove sites you don’t have permission to view from a menu), and windows authentication.

Solution:
We are going to create a custom role provider which will proxy request to the default windows role provider but will query sql to add in any temporary permissions.

Firstly you have to create a database to hold this information. I would recommend the following format:

SecurityID, UserName, RoleName, DateExpires

Now you need to create a stored procedure called something like GetRolesForUser(UserName) – this returns all the roles that the user has access to which haven’t expired – nice and easy.

Now to some proper coding – create a class called CustomRoleManager, and inherit from System.Web.Security.RoleProvider (You will need to add System.Web.dll, and System.Configuration.dll as a reference)

Hover over the role provider and tell it to implement the methods – it will add the following methods for you:


public override void AddUsersToRoles(string[] usernames, string[] roleNames)
public override string ApplicationName
public override void CreateRole(string roleName)
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
public override string[] GetAllRoles()
public override string[] GetRolesForUser(string username)
public override string[] GetUsersInRole(string roleName)
public override bool IsUserInRole(string username, string roleName)
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
public override bool RoleExists(string roleName)

Add the following private variable to the class:

private WindowsTokenRoleProvider m_oRoleProxy = new WindowsTokenRoleProvider();
In case the name doesnt give it away we are going to use this as a proxy for almost all of our calls – so for every function apart from GetRolesForUser(string username) simply proxy the call e.g.

public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
m_oRoleProxy.AddUsersToRoles(usernames, roleNames);
}
...
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
return m_oRoleProxy.FindUsersInRole(roleName, usernameToMatch);
}
And implement GetRolesForUser(string username) like this: (you will have to add using System.Collections.Generic;)


public override string[] GetRolesForUser(string username)
{
string[] strRoles = m_oRoleProxy.GetRolesForUser(username);
List<string> oRoles = new List<string>();
oRoles.AddRange(strRoles);
SqlDataSource sqlMyDataSource = new SqlDataSource( ConfigurationManager.ConnectionStrings ["MyConnectionString"].ConnectionString, "spGetRolesForUser");
sqlMyDataSource.SelectCommandType = SqlDataSourceCommandType.StoredProcedure;
sqlMyDataSource.SelectParameters.Add("@UserSID", username);
DataView dvMyData = (DataView)sqlMyDataSource.Select DataSourceSelectArguments.Empty);
foreach (DataRowView dvMyRow in dvMyData)
{
oRoles.Add(dvMyRow["RoleName"].ToString());
}
return oRoles.ToArray();
}
Now we need to modify the config file to use our brand new role provider

Simply add this into your web.config

<roleManager enabled="true" defaultProvider="CustomSqlRoleManager">
<providers>
<clear/>
<add name="CustomRoleManager" type="CustomRoleManager" connectionStringName="MyConnectionString" applicationName="AppName" />
</providers>
</rolemanager>

Compile, and you are sorted!!

Things to consider:

Really you shouldn’t use a username to store the data in the database, you should use a SID (think Guid for AD). Its a bit tricky to get this which is why I have left it out, basically you have to get it out of AD and convert it to a string. If you need to know how to do this then let me know and Ill show you.

Secondly you might have noticed the application name in the config file – this would allow you to use the same table in multiple different apps – so provide access to the role “admins” in one web site but not in another. Easy to implement – simply add the column in the db table and in your sp, and pass it through to your sp – it is stored in the property Application name within your custom class!

And finally you may be wondering why we only add the temporary roles into get roles for user and not is user in role for example, well the simple answer is I don’t know, what I do know is that when I tested the code the only bit called was get roles for user, so it wasn’t needed (User.IsInRole(“Role”) still used the getrolesforuser!!) – I think that the security manager is quite clever and holds the data in a cache when the page is first loaded, there is only ever one call per page – even though the site map, and parts of the page requested role information several times!

Any questions then let me know!

Ross

7 comments:

sathya said...

Very nice artcile. Appreciate your efforts. I have scenario where i need to use windows autentication but for roles i need to use Custom role provider, whether the same can be achived in such scenario. please help me.

Ross Dargan said...

Im a little confused Sathya, the code above does exactly what you have asked. This basically sits on top of windows authentication, and uses the roles you get by default from AD, and adds you into some custom roles.

Can you explain a little more about the issues you are having?

Thanks

Ross

Brian said...

Really dig this post - I was trying to find out if it was possible to do just this!

One question, and it may be dumb, but I'm just getting used to C# after spending most of my (short) career in Smalltalk and my schooling in C++. Would it be possible to get similar functionality by making the CustomRoleManager inherit from WindowsTokenRoleProvider and using base.AddUsersToRoles(...) and so forth? Not that the functionality would be different, just curious about implementation.

Brian

Nunas said...

It's a very good an article, Thnx for it.

SID said...

Nice one for a for a quick overview

Chad said...

Do you have a VB version of this?

lisa said...

Hey there, was just wondering if extending the RoleProvider means you HAVE TO implement the MembershipProvider as well?
Also, my authentication takes place through a webservice which returns all this information to me (roles etc), only if I pass username and password, any solutions??