
Introduction
This 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 code
The classes that are visible outside the library include:
SecurityManager - allows for authenticating a user based on a user-name and password.
UserManager - allows for creating and removing users from the database.
RoleManager - allows for creating and removing roles from the database.
RightManager - allows for creating and removing rights from the database.
UserRightManager - allows for creating and removing associations between users and rights.
UserRoleManager - allows for creating and removing associations between users and roles.
RoleRightManager - allows for creating and removing associations between roles and rights.
CustomIdentity - implements IIdentity and provides access to some additional user specific properties.
CustomPrincipal - implements IPrincipal and provides the ability to perform rights authorization.
SecurityException - allows the library to signal errors that are specific to the library.
SecurityDataException - allows the library to signal errors that are specific to the database access code.
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:
- Creating, deleting or updating a user - except for changing the password.
- Creating, deleting or updating a role.
- Creating, deleting or updating a right.
- Changing the role associations for a user.
- Changing the right associations for a user.
- Changing the right associations for a 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
);
}
The UserManager.Create method creates a new row in the cg_security_user table, and returns the new identifier. That is all you need to do in order to create a new user. Removing a user is just as easy; pressing the 'Del User' button causes this code to be executed:
private void _DoDelUser()
{
int userIndex = m_listBoxUsers.SelectedIndex;
int userID = (Int32)m_userTable.Rows[userIndex]["user_id"];
UserManager.Delete(userID);
}
The UserManager.Delete method simply removes the row from cg_security_user, along with any associated rows in other tables. The other manager classes perform similar functions in their own respective areas. It really is pretty easy to use the library, so I won't bother detailing every method of every class.
Points of Interest
I think the most interesting classes in the library are CustomIdentity and CustomPrincipal. These classes should be the primary interface between your code and this library. Since these classes are derived from IPrincipal and IIdentity respectively, you may use them in the same way you would a standard .NET security object. For those times when you want to use some other authentication scheme, you can still use this library, just use your current identity class to create a CustomPrincipal object. Provided the user is in the database, everything should still work correctly. I sometimes use this approach to give Windows users additional rights for my own devious purposes.
Just in case you are wondering, here is an example (again from the demo) of how to use the CustomPrincipal and CustomIdentity classes in your application:
private void MainForm_Load(object sender, System.EventArgs e)
{
try
{
LoginForm form = new LoginForm();
if (form.ShowDialog(this) != DialogResult.OK)
{
this.Close();
return;
}
if (!SecurityManager.Authenticate(form.UserName, form.Password))
{
MessageBox.Show("Failed to authenticate the user!");
this.Close();
return;
}
System.AppDomain.CurrentDomain.SetThreadPrincipal(
new CustomPrincipal(new CustomIdentity(form.UserName))
);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
this.Close();
}
}
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 CustomPrincipal like this (from the UserManager.UpdatePassword method):
if (!Thread.CurrentPrincipal.Identity.IsAuthenticated)
throw new SecurityException("User must be authenticated" +
" in order to perform this action!");
if (Thread.CurrentPrincipal.Identity.AuthenticationType == "Custom")
{
CustomIdentity identity =
(CustomIdentity)Thread.CurrentPrincipal.Identity;
if (identity.UserID != userID)
{
if (!Thread.CurrentPrincipal.IsInRole("Admin"))
throw new SecurityException("User must be in the Admin" +
" role to perform this action!");
}
}
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);
}
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:
- What roles (if any) have been associated with the user?
- What rights (if any) have been associated with those roles?
- What rights (if any) have been specifically associated with the user?
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:
- Let's say a user is a member of role 'A' and role 'B'.
- Let's also say that role 'A' has right '1', and role 'B' has right '2'.
- At this point, the rights for the user would be: '1' and '2'.
- If we then add a disabled association between the user and right '1', the rights for the user become '2'.
- If we go on to add an enabled association between the user and right '3', then the rights for the user become: '2' and '3'.
Conclusion
I 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
- Library version 2.4 - Original release on CodeProject.
- Library version 2.5 - Various small fixes for things that escaped me when I created the original article.
| You must Sign In to use this message board. |
|
|
 |
|
 |
Hi I use this security dll in my website. my host changed the "Trust Level" to Low.after that i give this error message. "Required permissions cannot be acquired"
I know that's about my Security dll but what should i do? i adde this line to AssemblyInfo file,but it dosn't work . [assembly: System.Security.AllowPartiallyTrustedCallers]
is there anybody help me? 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Wow good stuff martin it's just like what i wanted to do but lacking necessary knowledge to do it.This is great.I'm trying to use the library with MSSQL2005.i guessed i just have to change the namespace ACCESS to MSSQL , set the connectionstring default to SQLServer and have the same schema in the MSSQL database to get the thing done.But it seems not to be so.After i did it it compiles alright but i'm having and exception : Must declare scalar variable @username. up to now i can't find what is wrong.Has anybody tried it? I'll more that grateful if i have a clue from any one you thanks
eager to learn
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
/// /// Used to retrieve the last autonumber value for the specified table. /// /// The database transaction that was used to perform /// the last change to the table. /// The name of te table. /// The last autonumber value used. public static int GetLastValue(OleDbTransaction txn, string tableName) {
// Execute the SQL statement. return (Int32)OleDbHelper.ExecuteScalar( txn, CommandType.Text, "SELECT @@IDENTITY FROM " + tableName );
} // End GetLastValue()
I was wondering where you got the "SELECT @@IDENTITY FROM [tablename]"
i could not find any information on MSDN and other sites. adding the word FROM will return @identity for each record of [Tablename]. isnt it? i guess this is unescessary already. a plain select @@identity should work already.
comment
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I like it and it works, but I would suggest separating the core of the code from being aware of any specific database access.
For example,
The interfaces should not be aware of any version of implementation, yet these: System.Data.DataSet FindAll();
System.Data.IDataReader FindByPK( int roleID );
are referenced in your core interfaces - ouch!
The coded interface begins with a mapping directly to a database implementation and restricts the implementation (this is bad interface usage - IMO). What if I wanted to use a GUID? What if I am not going to use any version of a Dataset, or more specifically use the IDataReader interface. The interface should ONLY define what needs to be done, not how. A base abstract class should begin that stage.
For example, I would recommend abstract classes to be used within your interface for, let's say 'UserRole' and instead of returning an IDataReader for FindByPK(int roleId), you return the abstract interface of UserRole.
The same sould be done for 'int roleId'. Create an abstract class that defines RoleId and use that class.
Again (to be truly OOP and disconnected from implementation) you used:
System.Data.DataSet
Why not use System.Collections.CollectionBase for returning the collction of roles?
I like true OOP and the code works, and I appreciate that it is here, but believe that an interface, in this scenario, should only define the actions, not how the actions are performed and my hands are tied immediately with this implementation.
Furthermore, I belive this code is very much tied to your specific implementation staright form the interface on through.
This is decent code for specific use with DataSets, but I suggest that you reconsider the design to be far more generic and separate the implementation from the interfaces and inherited members.
Trevor
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hello, I try to set custom principal in VB2005 but AuthenticationType is still NTLM. The same code in VB 2003 and in C# 2005EE works fine: CG.Security.SecurityManager.Authenticate("admin", "") AppDomain.CurrentDomain.SetThreadPrincipal(New CG.Security.Principal.CustomPrincipal(New CG.Security.Principal.CustomIdentity("admin"))) MsgBox(Threading.Thread.CurrentPrincipal.Identity.AuthenticationType.ToString) Am I missed something?
Jaro
|
| Sign In·View Thread·PermaLink | 1.25/5 |
|
|
|
 |
|
 |
Hi i have the same problem. In particular : Dim cp1 As New CustomPrincipal(New CustomIdentity("xxx")) System.AppDomain.CurrentDomain.SetThreadPrincipal(cp1)
Now i aspect that Threading.Thread.CurrentPrincipal is of type CustomPrincipal. Instead if i call MsgBox(Threading.Thread.CurrentPrincipal.Identity.AuthenticationType) this is of type GenericPrincipal and cause an exception. Why this?
Note that on a new project it work fine? Please help me.
Giacomo De Rosa
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi again Martin, Thanks for the update, unfortunately I’ve found another bug which seems to be affecting all the listboxes. When deleting the bottom item from the User, Role or Right listboxes, an error message is displayed as follows:
Specified argument was out of the range of valid values. Parameter name: '2' is not a valid value for 'Value'.
Also (related) when deleting the last and only item from the User, Role or Right listboxes, the error message changes to:
Specified argument was out of the range of valid values. Parameter name: '0' is not a valid value for 'Value'.
Thanks in advance and King Regards Jerry
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
First of all, real good work man  I'm developing something really similar for my general purpose .NET library. In my personal opinion, you could achieve a more reliable and stronger architectural solution increasing objects abstraction. I mean refactoring the classes and abstracting their different aspects in many interfaces and splitting codes and abstract declarations on more inheritance levels. IMHO This concept should be applied on the "xyxManager" classes (inherit them from a common pattern). One thing i really don't like are the hard-coded queries in "UserData", "RoleData" and so on... it's really bad You could use (as i've done ) an xml configuration file accessed by a common manager; IMHO it's a really scalable, strong and easy-to-manage solution.
-- NinjaCross www.ninjacross.com
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
Hi Martin, Very good article. I'm not sure if I missing something, but I can't seem to change a user's password. Here's what I did. 1. Logged in as Admin and created a new user with password. 2. Logged out Admin and Logged in as the new user. 3. Selected the new user, and changed the password. 4. Logged out and back in as the new user, but I can only get back in with the original password.
Thanks in advance and King Regards
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Hello Jerry123,
Good catch! I'm not sure how that one slipped by 
I made an update and posted it to codeproject. Hopefully the updated zipfile will be available on the website soon.
Thanks for the great feedback!
Martin Cook Jesus answered, "I am the way and the truth and the life. No one comes to the Father except through me." John 14:6
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Hello Martin, I have really impressed your article.I would like to ask a question. I want to develop a software project with some custom security rules.How can I supply windows forms control based security to my users.This means users will access the windows forms controls based on their user rights. How can I start? If it possible could you please give me some advices about this, as a very experienced developer.. Thanks in advance and King Regards
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Hello innocent73,
Thanks for the kind words, I'm glad you like the article & library.
Integrating the library into a set of custom control isn't too hard. In the demo application I do some simple GUI customization by checking the role of the current user. A check such as that is performed like this:
Thread.CurrentPrincipal.IsInRole("Admin")
Of course, that example assumes that the current user's principal was previously setup. Here is an example (again from the demo) of how to perform that step:
System.AppDomain.CurrentDomain.SetThreadPrincipal( new CustomPrincipal(new CustomIdentity(form.UserName)) );
Both of these tasks could be performed in your custom control. Also, by downcasting the principal to an instance of CustomPrincipal you can check for individual rights using the CustomPrincipal.IsAuthorized method.
Once you make these type of checks you can have your controls respond however you like.
I hope my answer helped.
Martin Cook Jesus answered, "I am the way and the truth and the life. No one comes to the Father except through me." John 14:6
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Can you change code to be able do next?
[PrincipalPermissionAttribute(SecurityAction.Demand, Right="Test 1")] private void SomeFunction() {
}
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Good question. My first thought was to derive a new class from the PrincipalPermission class. However, that approach wont work since the PrincipalPermission class is sealed. I suppose my next approach would be to create a class that encapsulates an instance of PincipalPermission and defers to that instance for all the standard stuff - while adding the ability to deal with rights. Not the cleanest approach, but sealed classes can be a challenge t work around.
Of course, it would also be necessary to create a corresponding Attribute class for the new permission class. I haven't written this code yet but it's such a good idea that I probably will. I'll be sure to post the change here.
Martin Cook Jesus answered, "I am the way and the truth and the life. No one comes to the Father except through me." John 14:6
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Do you have plans of creating an asp.net demo version of this?
im currently using your code for my asp page but i have lots of worries here and there.
im currently implementing it and i wonder if you have done it in asp.net as well.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
I haven't had the time to do much CodeProject stuff lately, so I suppose the short answer is that I do not plan to create an ASP.NET demo.
I'm glad to hear that you are using my library. I'm not sure what worries you have, are they security related? The library leverages the standard ASP.NET security mechanisms so things should be fairly secure.
The library I published in this article is a simplified version of the code that I use professionally. I have used my private library in a variety of winform and ASP.NET projects. I do not use this library professionally.
If you have a specific question I'll try to answer as quickly as possible.
Martin Cook Jesus answered, "I am the way and the truth and the life. No one comes to the Father except through me." John 14:6
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
im still studying your work. Im not quite sure how current custom_principal will work on asp pages.
Will multiple users have different custom_principal?
anyways, i still could not make it work. im getting errors everywhere. no worries. im also stuying asp.net with C#.
thanx!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Each unique set of credentials (user name + password) would typically get a principal/identity. In a related article I showed how to use the library by authenticating the user name + password in the Application_AuthenticateRequest event handler. Once the user is authenticated, I then showed how to cache the principal + identity for the user in the current HTTP context object.
You can find the article at: http://www.codeproject.com/csharp/CGSecurity3.asp[^]
Martin Cook Jesus answered, "I am the way and the truth and the life. No one comes to the Father except through me." John 14:6
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
THANX!
i've been studying each code lately. found some interesting practices like obdchelper and storing configurations.
great article!
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
in class CG.Security.Principal.CustomPrincipal
in function CustomPrincipal(...){
line:
for (int x = 0; x < roleCount; x++) m_roles[x] = (string)ds.Tables[0].Rows[0]["role_name"];
must be:
m_roles[x] = (string)ds.Tables[0].Rows[x]["role_name"];
|
| Sign In·View Thread·PermaLink | 2.50/5 |
|
|
|
 |
|
 |
Thanks for the heads up xandrix, I'll update the project code ASAP.
Martin Cook Jesus answered, "I am the way and the truth and the life. No one comes to the Father except through me." John 14:6
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
hi mr cook i look for a User administration tool your Quick and easy user-level security checks is very suited. bur i want to have it in C++ can you help me more.
Naghmeh Semsar
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Sorry, I do not have a C++ version of this library available.
Martin Cook Jesus answered, "I am the way and the truth and the life. No one comes to the Father except through me." John 14:6
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Your app is very good. I was wondering how to access windows local user and pwd like windows user management. Do you have any idea? Thanks,
Jian
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
I thank you for the compliment.
As to your question, I don't know of any API or system tool that will provide the contents of a user's password. You could always launch a brute force dictionary attack against the SAM database, but I wont teach you how to do that... 
User names are somewhat more accessable, but not by much. In C# you can obtain the user name for the current user two ways:
#1: Thread.CurrentThread.CurrentPrincipal.Identity.Name
#2: WindowsIdentity.GetCurrent().Name
I believe WMI provides facilities for enumerating Windows users. It has been awhile since I’ve used WMI though, so I can’t recall how to do it. Try doing a Google search…
Hope my answer helped.
Martin Cook Jesus answered, "I am the way and the truth and the life. No one comes to the Father except through me." John 14:6
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|