Saturday, December 25, 2004 4:30 PM
bart
PSP Episode 4 - Role-based secury unleashed
Role-based security unleashed
Every .NET developer should (at least) have heard about role-based security. In this fourth episode of my "Personal Security Push" I'll talk about what role-based security is all about, how it works and how to empower it in your applications.
What is it?
Roles should build the bridge between the security built in in your code and the physical company structure (think of HR). It tells the code who can perform what action. In real life, your organization likely has various "roles", such as management, consultants, clerks, etc. These people all have different rights and therefore they need different access rules when performing actions in your application. The .NET Framework supports this by means of "role-based security", in which people are associated to their role. and security is applied based on that role. Typically you'll find roles useful to enforce business rules.
About authentication and authorization
In order to use an application, a user needs to do (either implicitly or explicitly) several things to get started. The first action is typically authentication. Authentication is a mechanism for the user to prove that he/she is really who he/she pretends to be (think of passwords, smartcards, fingerprints, eyescans, RFID identification, etc). In order to make this possible, the user and the system need to share a secret in order to be able to validate the user. When authentication succeeds, the system can examine the users database (whatever format it has, Active Directory, SAM, SQL Server, etc) possibly in combination with other repositories (AD/AM, AzMan, SQL Server, etc) to determine the roles for the user. As an example, Windows authentication uses the SAM or Active Directory to associate the groups with a user on logon (combined with privileges this results in the user token that can be queried via the whoami command). On the authentication phase has completed, authorization comes into play throughout the lifetime of the user's session, to determine whether the user has the appropriate rights (based on the user's identity and/or the roles of the user) to perform certain actions (e.g. access the filesystem, make changes to a database, or on a higher level, to perform certain business actions).
IPrincipal and IIdentity
The .NET Framework supports role-based security via the System.Security namespace in general. More specifically, there are two protagonists in the System.Security.Principal namespace that are crucial in the whole story:
- System.Security.Principal.IPrincipal: once the user has been authenticated, a principal for the user is created, which contains the user identity (IIdentity in property Identity) and the roles (method IsInRole) that the user belongs to; depending on the authentication type, a certain implementation of this interface is used (e.g. WindowsPrincipal for Windows authentication, GenericPrincipal for custom authentication)
- System.Security.Principal.IIdentity: the principal also contains information about the user himself/herself, i.e. the user's name and flags about the authentication type and a boolean indicating whether the user was authenticated or not (to detect anonymous users in an application, see Episode 3 on null sessions)
If it helps you, you can take a look at the principal as it were the managed equivalent of a user token, but it contains less information.
Assign the roles to a user
When using Windows authentication, the roles assignment for a user is performed automatically, based on the Windows groups the user belongs to. When accessing the WindowsPrincipal object you can query the roles of the users via the IsInRole method. When using forms authentication, you can use the GenericPrincipal object to represent the user:
FormsIdentity id = ...; //retrieve the identity in some way, e.g. by using FormsAuthentication.Decrypt on the authentication cookie
GenericPrincipal gp = new GenericPrincipal(id, roles); //roles is a string array with the roles
Context.User = gp;
Typically, you'll execute this kind of code after authentication or on Application_AuthenticateRequest event handler in global.asax. As complete example can be found on http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/SecNetHT04.asp.
Use the roles in ASP.NET
Once the roles are present, you can use these roles to make decisions about the user rights. In ASP.NET as possible way to do this is by using the web.config file:
<authorization>
<allow roles="role1" />
<allow roles="role2" />
<deny users="*" />
</authorization>
Combined with the location tag, you can grant only specific roles access to certain parts of the website:
<location path="/admin">
<authorization>
<allow roles="admin" />
<deny users="*" />
</authorization>
</location>
However, this is only the tip of the iceberg. In any kind of application, you can use the role-based security on the code level as well.
Declarative Security versus Imperative Security
Both on the class level and the member level (method, property, delegates, etc) you can apply declarative security. Declarative security is attribute-based (defined in System.Security.Permissions) and fairly straightforward:
[PrincipalPermission(SecurityAction.Demand, Role="SomeRole")]
public class MyClass
//...
or on the member level:
public class SomeClass
{
//...
[PrincipalPermission(SecurityAction.Demand, Role="SomeRole")]
public void SomeMethod()
//...
For Windows authentication, you'll use an attribute like this:
[PrincipalPermission(SecurityAction.Demand, Role=@"MYDOMAIN\TheGroup")]
Applying this attribute performs checking at runtime and if the current user's principal does not comply to the requirements in the attribute declaration, a SecurityException will be thrown.
An altnerative is imperative security. Using this mechanism, you write code to check the user's roles and take the right action based on the result. In fact there are two kinds of checks that can be performed at runtime. The first one is to "demand" security, just like we've done with declarative security. This will throw a SecurityException if the Demand call does not succeed. Another one is checking the role in a more flexible way using the IsInRole method. Let's show:
//Pure imperative security
new PrincipalPermission(null, "SomeRole").Demand();
//Manual role membership check
WindowsPrincipal wp = ...; //retrieve it principal in some way, in ASP.NET by using HttpContext.Current.User for example
if (wp.IsInRole("SomeRole"))
//do it
else
//throw an exception or take another action to tell the user that he/she is not allowed to perform this action
What mechanism to use?
Declarative security has the following (dis)advantages:
- Can be attached on the class level so that it applies to all members of the class. Imperative security is less flexible on this point.
- A security demand implemented through an attribute is executed before all the other code in the method, thus the code in the method has no chance to execute if the security demand fails. With imperative security, you have to do the work yourself and make sure it's right.
- You can query the security demands when doing deployment by using tools like permview.exe:
permview.exe /decl MyApp.exe
Microsoft (R) .NET Framework Permission Request Viewer. Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Class Test Demand permission set:
<PermissionSet class="System.Security.PermissionSet" version="1"/>
Class Test NonCasDemand permission set:
<PermissionSet class="System.Security.PermissionSet" version="1">
<Permission class="System.Security.Permissions.PrincipalPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1">
<Identity Authenticated="true" Role="test"/>
</Permission>
</PermissionSet>
Method Test::Do() Demand permission set:
<PermissionSet class="System.Security.PermissionSet" version="1"/>
Method Test::Do() NonCasDemand permission set:
<PermissionSet class="System.Security.PermissionSet" version="1">
<Permission class="System.Security.Permissions.PrincipalPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1">
<Identity Authenticated="true" Role="test"/>
</Permission>
</PermissionSet>
In IL (using ildasm.exe), you'll find the security permissionset demands in the beforefieldinit section of the class (or if applied on member level on top of the IL code):
.class private auto ansi beforefieldinit Test
extends [mscorlib]System.Object
{
.permissionset demand = (3C 00 50 00 65 00 72 00 6D 00 69 00 73 00 73 00 // <.P.e.r.m.i.s.s.
...
.permissionset noncasdemand = (3C 00 50 00 65 00 72 00 6D 00 69 00 73 00 73 00 // <.P.e.r.m.i.s.s.
...
.method private hidebysig static void Do(int32 a,
int32 b) cil managed
{
.permissionset demand = (3C 00 50 00 65 00 72 00 6D 00 69 00 73 00 73 00 // <.P.e.r.m.i.s.s.
...
.permissionset noncasdemand = (3C 00 50 00 65 00 72 00 6D 00 69 00 73 00 73 00 // <.P.e.r.m.i.s.s.
...
- Performance is better than with imperative security because it's evaluated only once when loading. This kind of optimization cannot be applied when the code is embedded inside your own code blocks.
- One of the drawbacks is that the security is embedded in the assembly and not configurable (which can also be an advantage). Be sure to look at the pitfalls section further in this post, since this can be a showstopper for declarative security.
Imperative security (dis)advantages:
- Based on variables (see pitfalls for more info on why this is great), can be configured using any mechanism you want to support.
- Your code is in charge of security, by means of condiational logic etc.
- Generally spoken, more flexible but less general and negative performance impacts.
More about System.Security.Permissions
I'll cover other attributes in this namespace in a later post. There are over 20 permission types in this namespace that can be used throughout your code to implement a real tight security in depth strategy. Check out next episodes on CAS (code access security) for more information about this.
Pitfalls
Be careful when using roles such as BUILTIN\Administrators. In localized versions of Windows, this name can be different (e.g. BUILTIN\Administradores or BUILTIN\Administrateurs). Make this configurable!
Del.icio.us |
Digg It |
Technorati |
Blinklist |
Furl |
reddit |
DotNetKicks
Filed under: Security