|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis article presents a library that enables a programmer to store and retrieve security credentials from a back-end database. The classes in the library also extend the .NET role-based authorization mechanism by introducing the concept of a "right". A right is a low level security element that can be assigned to a role or an individual user. The library includes a specialized principal class that allows these rights to be verified in much the same way that roles are today through the standard .NET security classes. The library will work with any compatible .NET language, and may be used in thin-client (ASP.NET) or thick-client environments. The library also exports a simple interface for managing users, roles, and rights. The database code is logically isolated from the rest of the library, so adapting things to work with other databases is very straightforward. Using the codeThe classes that are visible outside the library include:
The database is pretty simple, and includes the following tables:
As you have probably figured out, I created the default database using Microsoft Access. Remember, you can use any other kind of database by simply writing a bit of database code and modifying the application configuration file. In fact, I intend to write an article in the future that will describe just how to go about doing that. For now, I will describe how to use the library by looking at how it interacts with the demonstration application. The demo is certainly not going to win any UI design awards but it will allow you to play with the library. The demo starts by prompting you for a user-name and password, like this:
This is not just for show, the credentials you provide determine what you will be allowed to do once you get into the demo. Start by using 'admin' for a user-name and leave the password blank. The main screen of the demo includes a list of users. Selecting a user from the list populates the tabs on the right. Those tabs present the user properties, along with any roles and/or rights that have been associated with the user. You may use this demo to poke around and get a feel for the capabilities of the library. If you like, you can log into the demo as something other than the administrator. For instance, logging in as 'user' (again no password), will restrict some of the things you are able to do in the application. After all, it wouldn't do at all to have unauthorized users changing your security settings - right? There is a menu choice called 'Smart GUI' that looks like this:
Checking this menu creates some basic "intelligence" in the GUI that disables certain buttons whenever a user logs in that isn't a member of the 'Admin' role. I added this feature because the library will throw security exceptions if a non 'Admin' user attempts to perform certain actions. If you want to see these exceptions then turn the "Smart GUI" feature off. Just remember, the security exceptions are not bugs, they are deliberately thrown in order to prevent unauthorized users from making changes to the database. Here is a quick list of the library features that require the current user to be in the 'Admin' role:
The library requires the current user to be authenticated in order to change a user's password. In addition, if you are trying to change the password for a user other than yourself, you must be a member of the 'Admin' role. Everything else in the library may be performed by any user. Let's see how the demo interacts with the library. If you press the 'Add User' button, the following code is executed: private void _DoAddUser()
{
AddUserForm form = new AddUserForm();
if (form.ShowDialog(this) != DialogResult.OK)
return;
UserManager.Create(
form.UserName,
form.Password
);
} // End _DoAddUser()
The private void _DoDelUser()
{
// Get the current user index.
int userIndex = m_listBoxUsers.SelectedIndex;
// Get the identifiers.
int userID = (Int32)m_userTable.Rows[userIndex]["user_id"];
UserManager.Delete(userID);
} // End _DoDelUser()
The Points of InterestI think the most interesting classes in the library are Just in case you are wondering, here is an example (again from the demo) of how to use the private void MainForm_Load(object sender, System.EventArgs e)
{
try
{
// Prompt the user for credentials.
LoginForm form = new LoginForm();
// Did the user fail to provide credentials?
if (form.ShowDialog(this) != DialogResult.OK)
{
this.Close();
return;
} // End if the user failed to login.
// Did we fail to authenticate the credentials?
if (!SecurityManager.Authenticate(form.UserName, form.Password))
{
MessageBox.Show("Failed to authenticate the user!");
this.Close();
return;
} // End if the credentials were not authenticated.
// Set the default principal for the rest of the application.
System.AppDomain.CurrentDomain.SetThreadPrincipal(
new CustomPrincipal(new CustomIdentity(form.UserName))
);
} // End try
catch (Exception ex)
{
MessageBox.Show(ex.Message);
this.Close();
} // End catch
} // End MainForm_Load()
Once the principal is set, .NET uses it just as it would any other principal. When you perform security checks in your code, simply check for the // Check that the user is authenticated before we proceed.
if (!Thread.CurrentPrincipal.Identity.IsAuthenticated)
throw new SecurityException("User must be authenticated" +
" in order to perform this action!");
// If the calling application is using our identity class, then
// we can perform some additional verification.
if (Thread.CurrentPrincipal.Identity.AuthenticationType == "Custom")
{
// Get the identity class.
CustomIdentity identity =
(CustomIdentity)Thread.CurrentPrincipal.Identity;
// If the user isn't attempting to change their own password then we
// should verify that they are acting as an administrator before we
// proceed.
if (identity.UserID != userID)
{
// Check the role of the user before we proceed.
if (!Thread.CurrentPrincipal.IsInRole("Admin"))
throw new SecurityException("User must be in the Admin" +
" role to perform this action!");
} // End if the user should be an administrator.
} // End if we should verify the identity/role of the user.
Using the library to check for a user's rights could be done like this: private bool _CheckRight(string rightName)
{
if (Thread.CurrentPrincipal.Identity.AuthenticationType != "Custom")
return false;
CustomPrincipal cp = (CustomProncipal)Thread.CurrentPrincipal;
return cp.IsAuthorized(rightName);
} // End _CheckRight()
I demand to know my rights!So, how do you know what rights a user will end up with? Simple, begin by selecting a user, then answer these three questions:
The first and second questions are fairly straightforward, if you assign one or more roles to a user and those roles each have one or more rights assigned to them, then the rights assigned to the user will be the combination of the two lists. If you don't assign any roles to a user then they won't have any rights. If you don't assign any right to the roles then again the user wont have any rights. The third question is a little trickier because rights that are associated directly with a user override any other setting. Normally, you should probably stick to assigning rights through role associations, but every now and then, you may come across users that defy your ability to categorize them into a predefined role. In that case, you can turn a right on or off for a user by creating an association at this level and then enabling or disabling the association. Enabling it means the user will always have the associated right, disabling it means the user will never have the associated right. Here is an example:
ConclusionI didn't get involved in the internals of the library much since it is mostly standard database code with some plumbing thrown in to provide logical isolation from the rest of the library. I will talk about library internals more when I discuss how to implement a data access library for SQL-Server. Until then, play around with the demo application and consider how you might use the library in your next project. Have fun! :o) History
|
||||||||||||||||||||||