comments (not for humans)
This entry will explain the steps you need to take, to use ADAM for both roles and membership in ASP.NET 2.0 and authorization manager.

I read an article from Microsoft explaining how to use ADAM for roles (How To: Use ADAM for Roles in ASP.NET 2.0), and thought that I would then be able to use ADAM for both roles and membership without writing a single line of code. After much hassle, I found a blog entry stating that this is not possible: Problems with AzMan and ADAM (at least not with WS2003 SP1)

You can still make it work, but there are some steps you need to go through, and this also involves writing some code.

Initial steps

  1. Install ADAM, and setup ASP.NET 2.0 to use the ADAM instance as the Membership Provider (read my previous entry or check MSDN.)

  2. Go through Step 2 of the MSDN-article: How To: Use ADAM for Roles in ASP.NET 2.0



Writing a custom RoleProvider
As mentioned in the blog entry linked to above, the AuthorizationStoreRoleProvider only seems to work when you're using windows authentication. If you want to use forms authentication you will have to write your own RoleProvider as
the AuthorizationStoreRoleProvider is uanable to map the application users to the ADAM users. This may seem like a lot of work, but actually it is quite simple. Microsoft has written an aritcle on Implementing a Role Provider, which explains the methods you need to implement and the exceptions they should throw on errors.

To use authorization manager in your role provider, you should add the Microsoft.Interop.Security.AzRoles reference. This gives you access to a number of different classes. The most interesting are perhaps the AzAuthorizationStoreClass, the IAzApplication2, IAzTask and IAzRole.

To open a connection to the ADAM AzMan store, you can use the AzAuthorizaitonStoreClass and IAzApplication2:
AzAuthorizationStore _azStore = new AzAuthorizationStoreClass();
_azStore.Initialize(0, azManConnectionString, null);
IAzApplication2 _azApp = _azStore.OpenApplication2(azManApplicationName, null);

The IAzTask class, does not only represent the AzMan tasks, but also the Role-definitions. When creating a role, this means that you have use both an IAzRole and an IAzTask when creating and deleting roles in the application.
IAzTask task = _azApp.CreateTask(roleName, null);
task.IsRoleDefinition = true;
task.Submit(0, null);
IAzRole role = _azApp.CreateRole(roleName, null);
role.addTask(roleName, null);
role.Submit(0, null);

When checking the roles of a user, you need to provide the SID of the user. This is done in the following way:
MembershipUser user = Membership.GetUser(username);
IAzClientContext = _azApp.InitializeClientContextFromStringSid(
user.ProviderUserKey.ToString(), 1, null);
object[] roles = (object[])context.GetRoles("");
When adding or removing a user from a role, you can use the IAzRole class. In Authorization manager this class actually represents the role assignments. To add a user to a role, to the following:
MembershipUser user = Membership.GetUser(username);
IAzRole role = _azApp.OpenRole(rolename, null);
role.AddMember(user.ProviderUserKey.ToString(), null);
role.Submit(0, null);
Using the code parts above together with the MSDN article, should allow you to implement a working role provider storing both roles and users in ADAM.

Configurating ASP.NET 2.0 to use the new role provider
To use the role provider with ASP.NET 2.0, simply add a connectionstring (I assume and suggest you use a connection string to set the URI of the AzMan ADAM store), and the following to your web.config:
<roleManager cacheRolesInCookie="true" defaultProvider="RoleManagerAzManADAMProvider">
<providers>
<add connectionStringName="connectionstringname" applicationName="ONE_ABB" name="RoleManagerAzManADAMProvider" type="Package.Class, Assembly"/>
</providers>
</roleManager>

The parts you need to change, are the name of the connection string, and the type attribute. The type attribute contains the package name and class of the role provider you just created. If the class is in another assembly you, also need to add the assembly name. If it is in the same assembly, you can ommit the "," and assembly part.

Final comments
I guess Microsoft will provide a proper provider in time, but this should work for now. Please note though, that the users added to the roles will not show up in the azman.msc, and you will not be able to add users to the roles in azman.msc either.
edgar

Helps with the class membership !!!!

He article this very good, but I have a problem, when trying to obtain an user with the method GetUser() of the class membreship it returns hole, as if the user didn't exist,and if it exists.
You can to say if you know that it can be happening?

It excuses, my English is not very good
Mike

Help in General

I could easily follow the information in the Microsoft article on "Use ADAM for Roles in ASP.NET 2.0", but I'm a little confused with your changes. I'm new to ASP and VS2005, but I can't make sense on where to but these code changes. Any help ?

Thanks,
Mike
Erlend Oftedal

Re: Help in General

To allow ASP.NET to use Roles when the users are not windows users, you need to implement your own RoleProvider and this is where you can use the code above. The article http://msdn2.microsoft.com/en-us/library/8fw7xh74.aspx from Microsoft gives a good explanation to what the different methods should do. In general, open a connection to the store in the Initialize-method and, fetch data from the store in the others.
Jim Smith

Um, being new to ASP.NET 2...

I'm trying to do this now but keep getting muddled up. I'm a VB programmer at heart (for my sins) and so i'm kind of struggling to pick up c# at the same time as building a role provider? This might be a tad cheeky, but has anyone got the source code for an adam/azman role provider that they've got working? Or even part of one I could use as an example?

I'll be checking back over the next day or two...
Erlend

Re: Um, being new to ASP.NET 2...

I'm not that fluent in VB myself. But I guess you can find some interesting code here:
http://windowssdk.msdn.microsoft.com/en-us/library/ms719273(VS.80).aspx
Erlend

Re: Cannot get this to work.

Sorry, but I had to temporarily remove your last comment as there is some bug in my blogging software.
I don't have my Visual Studio where I am now, but I'll have a look at it later.
Mike Danielski

Nested Roles Resolution

Great article. I created my own provider as you described, and am able to creat/delete roles, add users to roles and get user role information etc. I am using Forms Authentication with ADAM Principals and AzMan roles all the way.

There are a couple of weird issues that I just can't find a solution to that I would love some feedback on. Those issues are as follows:

First, I am not able to get the "nested" role capability to work. Or maybe I misunderstand the technology - should this realy be phrased as "nested" application groups? I have added a Role to another Roles definition, and thought that my GetRolesForUser call would show BOTH, but it only shows the Roles to which I have directly added the ADAM principal. THoughts on that?

Secondly, I have configured the connectionStringName and applicationName properties on the provider, and set those properties in the web.config file where I use this provider. The strange thing is that the provide NEVER reads the parameters set in the web.config file, so I had to hard code them. What am I missign here?


Here is my Provider code (not *quite* finished, but it will get you rolling. Hope this saves someone else some pain!

Imports System.Configuration.Provider
Imports Microsoft.Interop.Security.AzRoles
Imports System.Web.Security


Public Class AzManFormsProvider
Inherits System.Web.Security.RoleProvider


#Region "Authorization Related Methods"

Public Overrides Function IsUserInRole(ByVal username As String, ByVal roleName As String) As Boolean
'Call the embedded method for retrieving the Roles list for this user.
Dim roles() As String = Me.GetRolesForUser(username)
Dim m_blnFoundMatch As Boolean = False

'Loop through the results to see if there is a match.
For Each objRole As String In roles
If objRole.Equals(roleName, StringComparison.OrdinalIgnoreCase) Then
'A match was found.
m_blnFoundMatch = True

'Exit out of the routine.
Exit For
End If
Next

'Return the value.
Return m_blnFoundMatch
End Function

#End Region


#Region "Methods dealing with fetching User's role infromation"

Public Overrides Function GetRolesForUser(ByVal username As String) As String()
'Get a connection to the AzMan application.
Dim _azApp As IAzApplication2 = ConnectToAzManApplication()
Dim user As MembershipUser = Membership.GetUser(username)
Dim context As IAzClientContext = _azApp.InitializeClientContextFromStringSid(user.ProviderUserKey.ToString, 1, Nothing)

'Get the roles in an object array.
Dim roles() As Object = CType(context.GetRoles(""), Object()) 'This is using an empty SCOPE in the GetRoles method.

'Convert all of the internal contents to the String represenation for returning from the function.
Return Array.ConvertAll(roles, New Converter(Of Object, String)(AddressOf GetRoleStringRepresentation))
End Function


Public Shared Function GetRoleStringRepresentation(ByVal CurrentRole As Object) As String
Return CurrentRole.ToString
End Function


#End Region


#Region "Methods for Creating, Deleting and Managing Roles"

Public Overrides Sub CreateRole(ByVal roleName As String)
'Get a connection to the AzMan application.
Dim _azApp As IAzApplication2 = ConnectToAzManApplication()

Dim task As IAzTask = _azApp.CreateTask(roleName, Nothing)
task.IsRoleDefinition = True
task.Submit(0, Nothing)

Dim role As IAzRole = _azApp.CreateRole(roleName, Nothing)
role.AddTask(roleName, Nothing)
role.Submit(0, Nothing)
End Sub


Public Overrides Function DeleteRole(ByVal roleName As String, ByVal thrownOnPopulatedRole As Boolean) As Boolean
'Get a connection to the AzMan application.
Dim _azApp As IAzApplication2 = ConnectToAzManApplication()

'Delete the role.
_azApp.DeleteRole(roleName, Nothing)

'Delete the associated Task.
_azApp.DeleteTask(roleName, Nothing)
End Function


Public Overrides Function RoleExists(ByVal roleName As String) As Boolean
'Get a connection to the AzMan application.
Dim _azApp As IAzApplication2 = ConnectToAzManApplication()
Dim roles(_azApp.Roles.Count - 1) As String
Dim m_blnFoundIt As Boolean = False

'Loop through the results and add the members.
For i As Integer = 1 To _azApp.Roles.Count
'Get a reference to the current role.
Dim objCurrentRole As IAzRole = _azApp.Roles(i)

'Add the role to the array.
If String.Equals(objCurrentRole.Name, roleName) Then
m_blnFoundIt = True

'Exit out of the routine.
Exit For
End If
Next

'Return the value.
Return m_blnFoundIt
End Function


Public Overrides Sub AddUsersToRoles(ByVal usernames As String(), ByVal roleNames As String())
'Get a connection to the AzMan application.
Dim _azApp As IAzApplication2 = ConnectToAzManApplication()

'Loop through all of the users and roles to add them as needed.
For Each objCurrentUser As String In usernames
'Loop through each of the inner role members.
For Each objCurrentRole As String In roleNames
Dim user As MembershipUser = Membership.GetUser(objCurrentUser)
Dim role As IAzRole = _azApp.OpenRole(objCurrentRole, Nothing)
role.AddMember(user.ProviderUserKey.ToString, Nothing)
role.Submit(0, Nothing)
Next
Next
End Sub


Public Overrides Sub RemoveUsersFromRoles(ByVal usernames As String(), ByVal roleNames As String())
'Get a connection to the AzMan application.
Dim _azApp As IAzApplication2 = ConnectToAzManApplication()

'Loop through all of the users and roles to remove them as needed.
For Each objCurrentUser As String In usernames
'Loop through each of the inner role members.
For Each objCurrentRole As String In roleNames
Dim user As MembershipUser = Membership.GetUser(objCurrentUser)
Dim role As IAzRole = _azApp.OpenRole(objCurrentRole, Nothing)
role.DeleteMember(user.ProviderUserKey.ToString, Nothing)
role.Submit(0, Nothing)
Next
Next
End Sub


Public Overrides Function GetUsersInRole(ByVal roleName As String) As String()
Throw New NotSupportedException
End Function


Public Overrides Function GetAllRoles() As String()
'Get a connection to the AzMan application.
Dim _azApp As IAzApplication2 = ConnectToAzManApplication()

'Declare local variables.
Dim roles(_azApp.Roles.Count - 1) As String

'Loop through the results and add the members.
For i As Integer = 1 To _azApp.Roles.Count
'Get a reference to the current role.
Dim objCurrentRole As IAzRole = _azApp.Roles(i)

'Add the role to the array.
roles.SetValue(objCurrentRole.Name, i - 1)
Next

'Return the value.
Return roles
End Function


Public Overrides Function FindUsersInRole(ByVal roleName As String, ByVal usernameToMatch As String) As String()
Throw New NotSupportedException
End Function

#End Region


#Region "Application Group Management Methods"

Public Sub CreateApplicationGroup(ByVal groupName As String)
'Get a connection to the AzMan application.
Dim _azApp As IAzApplication2 = ConnectToAzManApplication()

'Create the group.
_azApp.CreateApplicationGroup(groupName, Nothing)
End Sub

#End Region


#Region "STORE CONNECTION METHODS"

Public Function ConnectToAzManApplication() As IAzApplication2
'Declare local variables.
Dim _azStore As AzAuthorizationStore = New AzAuthorizationStoreClass
_azStore.Initialize(0, Me.connectionStringName, Nothing)

Dim _azApp As IAzApplication2 = _azStore.OpenApplication2(Me.ApplicationName, Nothing)

'Return the value.
Return _azApp
End Function


#End Region


#Region "PROPERTIES..."

Private p_applicationName As String
Public Overrides Property ApplicationName() As String
Get
If String.IsNullOrEmpty(p_applicationName) Then
Return "SchoolSystem"
Else
Return p_applicationName
End If

End Get
Set(ByVal Value As String)
p_applicationName = Value
End Set
End Property


Private p_azManConnectionString As String
Public Property connectionStringName() As String
Get
If String.IsNullOrEmpty(p_azManConnectionString) Then
Return "msldap://bmsserver:389/CN=AzManADAMStore,OU=SchoolSystem,O=behaviorsystem,C=local"
Else
Return p_azManConnectionString
End If
End Get
Set(ByVal Value As String)
p_azManConnectionString = Value
End Set
End Property

#End Region

End Class
Mike Danielski

Sample Syntax

Could you provide a small sample of the actual usage syntax? I can\'t get the console app to properly run, and I am sure that the problem is my use of syntax, as in the LDAP syntax, or other stupid things like that. For example, is the username JUST the name, or the full DN? Things like that have got to be the tripping points for my app.

Thanks!
Erlend

Re: Nested Roles Resolution

Regarding nested roles, I have not been working on this myself, so I sorry to say don't have any input for you there.
With regard to the web.config, you will have to create a initializer for your provider. This method will automagically be invoked when the framwork creates the provider. In C# the method signature is:
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
The config parameter will contain all the attributes from web.config, making them accessible to your code. You will have to extract them and store them in local variables. The connectionstring should be available through something like:
string cString = ConfigurationManager.ConnectionStrings[config["connectionStringName"]].ConnectionString
Erlend

Re: Sample Syntax

The IAzClientContext object has a method called AccessCheck. In the AzMan-store you can create tasks, roles and operations. Basically a task is a collection of operations, and roles are allowed to perform tasks. AccessCheck takes a lot of operations, but I have just used the following parameters in my apps:
object[] result = (object[]) clientCtx.AccessCheck(
auditIdentifier, // The name used in auditing
internalScopes, // Scopes
operationIds, //operationIds[0] = myopid = 3
null, null, null, null, null // used for BizRules and Role filtering
);

The parameter operationIds is an array of integers. Each operation is given an operation number when created in the AzMan store. The returned result is an integer array with one entry for each operationId, where 0 represents "access granted".

This is a good source for more information: http://msdn2.microsoft.com/en-us/library/aa480244.aspx#azmanapps_topic3_resourcesazmanapps_topic3_resources
Raj

Users added through Directory Servies aren\'t accessible through Membership

Hi,
I have done the setup of ADAM and the membership provider as per your article. I am able to add users to ADAM using something following syntax.
Membership.CreateUser("Rajesh", "MyPassword123!");
I am also able to add users to ADAM using following lines.

DirectoryEntry parent = new DirectoryEntry("LDAP://localhost:50000/OU=Users,O=TestDirectory", null, null, AuthenticationTypes.Secure);
DirectoryEntry user =
parent.Children.Add("CN=John", "user");

using (user)
{
user.CommitChanges();
}

When I look at ADAM I see both the users "Rajesh and "John" there.
But if I do Membership.GetAllUsers and I do a count, it shows only 1 (only Rajesh). But if I do the following, I get to see all the users.

DirectoryEntry parent = new DirectoryEntry("LDAP://localhost:50000/OU=Users,O=TestDirectory", null, null, AuthenticationTypes.Secure);

//following line works fine and is able to find the user Rajesh.
parent.Children.Find("CN=Rajesh");
//following line works fine and is able to find the user John.
parent.Children.Find("CN=John");

So why do I see only the users added through Membership using Membership when in fact DirectoryServices is able to find users added through both Membership and DirectoryServices?

Please help.
Thank you...
Regards...


P

AzAuthorizationStoreClass error!

Hi there,

Great article. I am trying to make it work but when I want to
AzAuthorizationStoreClass AzManStore = new AzAuthorizationStoreClass();
I get this error:

The handle is invalid. (Exception from HRESULT: 0x80070006 (E_HANDLE))

Do you know what is cousing this error?

Also when I want to Initialize AzManStore I get this errot:

The specified network provider name is invalid. (Exception from HRESULT: 0x800704B4)

I hope you can help me solve these problems.

Thanks.

NewToADAM

Get errors when trying to add existing user to a existing role

I get following error message when I try to add existing user to a existing role

Exception: Exception has been thrown by the target of an invocation.
Inner Exception: The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))

I add the users using the following code segment

MembershipCreateStatus status;
Membership.CreateUser(userName, password, userEmail, passwordQuestion, passwordHint, true, out status);

I query the users using the following code

Membership.GetAllUsers();

Only problem is when I try to add the existing user (created using the above code segment) to a existing role, it throws the error

i.e. userName = "testuser", roleName = "Manager"

Code: Roles.AddUserToRole(userName, roleName);


Note: Manager role exist
Ganesh

ASP.Net Roles

Iam pretty new to Membership and Role Management in ASP.Net, hope you will be right person in helping me out.

As per my knowledge, assigning permissions to roles can be done at folder level.

I have to build a custom Website administrator tool which uses the ASPNetDB database(available with .net framework), it should have an option to create roles and edit them in such a way that the users should be able to assign permissions to the roles at page level and also at functionality level within that Page for each role.

Please mail me at gannyprodigy@gmail.com
Erlend

Re: ASP.Net Roles

If you are using the ASPNetDB database, you should be able to use the built-in roleprovider as far as I know. Try right clicking the web site in Visual Studio and use the configuration wizard.
David
Hello Erlend, sorry to bother you with this very old post but I can really use your help. I've implemented the full membership provider and it works MOST of the time, however from time to time it breaks with an ugly runtime exception, COMException - Cannot create a file when that file already exists.

From what I've found that can happen during concurrency and if the store is XML based, however my store is LDAP (ADAM/AD-LDS) based.

I'm worried about my code since the _azApp never gets explicitly disposed and it's created on every method implemented for the provider. Should I worry about that? or in your opinion my problem should be only related to the infrastructure?

Thanks a lot.
Erlend
@David: Hmm. COMExceptions are tricky. I would guess disposing would be a step in the right direction. We had some weird problems with DirectoryEntry objects earlier stemming from the fact that in one code path they were not properly disposed.
Comments closed for this post