Larry,
Ivan referred you to a post of mine from about a million years ago, when I was first investigating SF and RBS. You definitely should check it out, but I can add some cliff notes (which normally mean a condensed version of the material, making it easier to understand...this might be more like a steep drop that causes pain if you fall off of it) that might also help.
First and fore most, it worked.
The app has been in production using SF and the RBS for 1+ years. This is definitely possible.
There are a couple of key concepts:
- The permissions for a user are set when they login. They don't change within the application after that.
- The RBS has no mechanism to handle storing separate roles/permissions based on project. It just isn't there.
This leads to some design decisions. First, you'll need to add some of your own tables (and associated BOs) to track at least the role(s) a user has for a project. You might also track what permissions a user has per project (to allow a user to be assigned permissions directly, like SF does...I skipped this, using only roles). If you're users can be assigned to projects (i.e. they aren't automatically assigned to them all), you'll likely need to track which projects a user is assigned as well. I also track the default project of a user, which is the one they get logged onto by default when they login. About every 6 months I come up with the idea to just put a drop down on the login form to let them choose which project, but then remember that since my users aren't automatically allowed access (or even know about) all projects, there is no way form me to know what to put into the project drop down until after they have logged in. If your project uses windows authentication or all users have access to all projects, this might be a good way to go though.
Next, you'll need to dynamically assign the roles a user has
after they have been authenticated
and you know what project they are going to be working on but
before they've actually logged in (which is when the permissions get set within RBS). You uses the SFS BOs to clear any potentially conflicting roles, then use your lookup table to load the appropriate roles for the user/project. After you've done this, you log them in, which loads the app with the appropriate permissions. This means that you will need to authenticate them, get a project, clear and load roles/permissions, then log them in for real. Here is the OK button handler in my custom login form.
Private Sub btnOk_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOk.Click
'-- Authenticate the user
Dim userInfo As SFSUsersBO = Nothing
Dim loginResult As Login.LoginResult
Using userInfo
loginResult = Login.AuthenticateUser(Me.Username, Me.Password, "", userInfo)
'-- Based on result, we need to configure the app.
' The shared UserContext class handles the hard work of
' determining the default project, then setting permissions, etc.
Select Case loginResult
Case Login.LoginResult.Success
UserContext.SetUserContext(LoggedInUser.CreateNew(userInfo))
Case Login.LoginResult.AdminLoggedOn
UserContext.SetUserContext(New AdminUser(Now))
Case Login.LoginResult.SecMaintUserLoggedOn
UserContext.SetUserContext(New SecurityMaintenanceUser(Now))
Case Login.LoginResult.Failure
End Select
End Using
'-- Raise the attempt login event
' This will handle any messages for user who's logon failed.
Me.OnAttemptLogin()
End Sub
In UserContext.SetUserContext, I do the following (I've just included the comments to give you an idea of the things I'm doing and not make this too long of a post):
Public Shared Sub SetUserContext(ByVal secUser As ISecurityUser)
'-- Determine if the user is an enterprise level or project level user
'-- Save the User's ID, which is needed by other procedures in this class
'-- Determine if the user is an admin
'-- Load the project list for this user
'-- Determine the user's default project. If none is set, and the user
' has access to at least one project, we set a default for them.
'-- If there is still no default project, and the user is a project level
' user, then we can't continue. Notify them and exit.
'-- If there is a default project, set it. SetCurrentProject handles
' setting roles based on project.
If defaultProjectID <> 0 Then
SetCurrentProject(defaultProjectID)
End If
End Sub
In SetCurrentProject, I actually handle the setting of permissions. This is in a different method because it is also used when a user changes projects (selected from a list of available projects). Also note that I have a class of users who have "enterprise" level access. This means that they automatically have access to all projects and share permission/roles across all those projects. You may not have this type of user. Here is the code for SetCurrentProject:
Public Shared Sub SetCurrentProject(ByVal projectID As Integer, ByVal reloginUser As Boolean)
'-- Does user have access to all projects?
Dim userHasAllProjectAccess As Boolean = (_allProjectAccess = PermissionAction.Grant)
'-- Check that the parameters are valid, i.e. is this project
' ID valid for the user. There are two possible causes
' for problems here. The first is that the default project
' table is corrupt and is pointing to a project that has
' been deleted or was imported from another server and
' doesn't exist. The other is that somebody is trying to
' break into the system
If Array.IndexOf(_userProjectList, projectID) < 0 Then
Throw New ArgumentException("The project ID provided is invalid." _
, "projectID" _
, New Exception("The user does not have access to this project"))
End If
'-- Cache the project ID so it is easily accessible.
_currentProjectID = projectID
'-- Cache details about this project (client), so it is easily accessible
' to forms/methods that need the current project.
Using boProject As New Project.ProjectBO
boProject.FillByPrimaryKey(projectID)
_currentProjectName = boProject.ProjectName
'-- Other data about project you might need...
'-- Notify subscribers that project state has changed. This
' notifies any open forms that the project has changed.
boProject.NotifyProjectDataChanged()
'-- Set the current BO (which allows above to occur)
_currentProjectBO = boProject
'-- Configure the bo to monitor for data changes
AddHandler _currentProjectBO.ProjectDataChanged, AddressOf CurrentProject_DataChanged
End Using
'-- Handle setting roles
If Not userHasAllProjectAccess Then
'-- First clear any existing roles for the user.
ClearUserProjectRoles()
'-- Now set the roles defined
' Get the roles that can be defined by project
Using userProjectRoles As New SecUserProjectRoleBO
userProjectRoles.FillByUserProject(UserContext.CurrentUserID, _currentProjectID)
'-- For each role, add it to the SF user roles linking data
For Each projectRole As Data.DataRow In userProjectRoles.CurrentDataTable.Rows
'-- Open a BO to the SF user roles data
Using userSFRolesBO As New SFBO.SFSUsersXRolesBO
userSFRolesBO.FillAllByUser(UserContext.CurrentUserID)
userSFRolesBO.Add()
userSFRolesBO.ur_rl_pk = DirectCast(projectRole.Item("RoleID"), Integer)
userSFRolesBO.ur_us_pk = UserContext.CurrentUserID
userSFRolesBO.Save()
End Using
Next
End Using
End If
'-- If indicated, login user in (needed if user is changing projects)
If reloginUser Then
'-- Handle the case of admin users, who don't need refresh
If SecurityBasics.CurrentUser.IsAdministrator Then Return
'-- Login the user in again, which updates permissions
Using userSFBO As New SFBO.SFSUsersBO
userSFBO.FillByPrimaryKey(SecurityBasics.CurrentUser.UserPK)
LoggedInUser.SetLoggedOnUser(userSFBO)
End Using
End If
End Sub
I included the code here as this is were the work is actually getting done. Once this is done, my user is logged in, they have been assigned the appropriate roles for the project and I have a shared class that defines the project context for them.
Most of the rest of the development is pretty normal, because my user is now correctly assigned permissions. Normal RBS security just does its thing.
However, there are now a large set of forms and associated BOs that need to also be project specific. I.e. RBS won't know within a form that it needs to track projects, and thus reload permissions (which would be very unwieldy also). So, most of my forms (there are some that aren't project specific, like the one that defines projects!) are based an a project base form that has a ProjectID property. These forms requires that the project ID be set. Any data loaded must be within this project. Then, the permissions will be valid. In my menu system, I simply construct the form using the UserContext.CurrentProjectID.
This also means that most of my BOs have a FillByProjectID method.
You'll also have to make sure your browse dialogs would need a "static" filter on the project ID, that is you would need to set the project ID based on the current project and the user would not be able to change it. When I started, the browse dialog didn't support this, so I ended up rolling my own (great learning experience...but I'd use SFs now).
You'll also need UIs to create/manage projects, assign users to projects, assign roles/permissions to users for a project and to allow the user to change projects.
I also clean up there roles/permission in the SFS tables. This is redundant, as I'm also cleaning it up when they login, and I'd do a bit of investigation to see if I really need to be that anal.
I also have roles that are project specific and those that are not. The ones that are not project specific are things like access to the project definition data/forms, access to enterprise wide data (such as standards that might be used across all projects), etc. You could just use the admin role for that sort of stuff, but with the standards stuff, we needed to let content experts manage that. In some cases, they have NO access to any project related data. This means that I also track roles that are project specific. Thus a user could be a project specific user
and have access to selected enterprise features. The ClearUserProjectRoles method thus actually only clears roles that are project specific, leaving any enterprise level roles alone.
Well, that turned into a book....Please let me know if you have any more questions or if you have any thoughts on how this might work better. This was the first thing I did in SF/.Net, during my trial, as I needed to know if this could be handled before committing to SF as the tool to use. As a result, there might be options I didn't consider/code that could be improved upon