Powered By Blogger

Friday, November 18, 2011

Managing Forms Authentication Access Rules programmatically

Introduction:

Forms Authentication is not one of the default authentication mechanisms used by the IIS 5 and 6 servers and was introduced together with ASP.net applications. Forms authentication is aimed mainly at internet facing ASP.net web-applications. It allows them to be able to authenticate users without having to rely on a corporate directory such as Active Directory.

The way in which forms authentication grants access to different parts of an ASP.net application is based on a system of permissions described in the web-application's web.config file. It is not the purpose of this article to discuss in detail the section of the web.config that is responsible for attributing the permissions or how ASP.net forms works. If you want further reading on these topics, I would recommend:

Rather the article focuses on showing how you can programmatically go about adding or changing these access rules from inside your own application.

Scenario:

Suppose that you built an ASP.net web-application that had the following folder structure:

To continue with the scenario, we will suppose that you have two roles, one called Administrators and one called SiteUsers. All authenticated users for your web-site will be part of the SiteUsers role and only the site Administrators will be part of the Administrators role. We would like to achieve the following:

When the ASP.net application is started up, we would like to verify that:

  • only Administrators have access to the AdminFolder of the application. If not we would like to change this so that the policy is enforced.
  • only SiteUsers and Administrators to have access to the UsersFolder of the application, and if this is not the case, we need to change the access rules to ensure that the scenario is met.
  • finally, that everyone, even non-authenticated users can have access to the PublicFolder.

There are two ways of doing this: one is to code all these rules directly in the web.config section of the application and then deploy it. The second way is to programmatically verify that the rules are in the web.config file and if they are not there, to add them.

Implementation:

The first thing to realize is that an ASP.net web application can have web.config files at multiple locations. Each web.config file describes the settings for the application that apply to the folder where the web.config is located and to the folders underneath. It also may overwrite or inherit settings from web.config files that are above its location in the folder hierarchy. Thus, the web.config file in the AdminFolder will inherit settings described in the web.config found in the PublicFolder.

To implement the scenario above, we must overcome the following obstacles:

  • how to access information that is stored in the web.config file
  • how to get a hold of the section that describes the authorization rules for forms authentication
  • how to distinguish between rules declared at the level of the current web.config and rules inherited from the web.config above.
  • How to store the information that we may need to add back into the web.config.

Accessing the web.config:

To access the web.config, the .Net development team has kindly provided us with a class from which we can instantiate objects that represent a .config file. In the case of our web-application we will use the WebConfigurationManager class with its OpenWebConfiguration(System.String) method. The argument of the method allows us to specify the location of the web.config file we wish to target. For example "/" would tell ASP.net that we would wish to target the web.config file that is at the root of our application. In this case the root of our app is the folder called PublicFolder.

The call to the WebConfigurationManager.OpenWebConfiguration(System.String) will return an object of type System.Configuration.Configuration which represents the web.config that we have just opened.

Configuration config =
WebConfigurationManager.OpenWebConfiguration("/");

The Configuration object allows us to get a hold of any section in the web.config we might need to read or modify. This is simply done via the GetSection(System.String)method. We can thus write the following code:

AuthorizationSection auth =
(AuthorizationSection)config.GetSection("system.web/authorization");

We see that just like everything else in .Net, a section of the web.config is still an object. And we have several types of sections, the one that interests us today being of type AuthorizationSection. Looking at the tag in web.config, we notice that it contains a series of inclusion and / or exclusion rules that grant or deny access to users and roles to parts of the application. Following the model, the AuthorizationSection object contains a list of AuthorizationRule objects. To inspect the rules that are present at each folder level, all we have to do is iterate through the Rules of the AuthorizationSection. For the public folder the code would look like this:

foreach (AuthorizationRule ar in auth.Rules)
{
//iteration logic will go here...
}

We thus verify that the following two rules exist:

  • We should allow all un-authenticated users access to pages in this folder
  • We should also allow all authenticated users access to pages in this folder.

Should the rules not be found, we would need to add them to the list of rules. We can do this by declaring a new object of type AuthorizationRule and then setting its properties accordingly. The code below shows how this would be done for an access rule allows all un-authenticated users to access the site:

//create the rule - allow access to un-authenticated users
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Allow);
accessRule.Users.Add("?");
auth.Rules.Add(accessRule);

Saving changes to the configuration is simply done by using the Configuration object's Save(System.Configuration.ConfigurationSaveMode)method. When using the ConfigurationSaveMode.Minimal we will only save the properties that differ from the ones that we inherit. This will become more obvious when we deal with the web.config files that are underneath the root one in this scenario:

config.Save(ConfigurationSaveMode.Minimal);

Now that we have ensured that the permissions are correct for the PublicFolder, we can focus on the AdminFolder. Here we want only users that are part of the Admin role to be able to access pages and all the rest (authenticated and non-authenticated users) should not have access. The access rules are stored in a web.config located in the AdminFolder, underneath the PublicFolder. If we look at this config file we will notice that normally only the sections that differ from the web.config that is at the root of the application are present. To recover the section, we can create another Configuration object and load it providing the appropriate path: "/AdminFolder":

Configuration config =
WebConfigurationManager.OpenWebConfiguration("/AdminFolder/");
AuthorizationSection auth =
(AuthorizationSection)config.GetSection("system.web/authorization");

The inspection of the rules is done just as we have for the previous example – we iterate through all the AuthorizationRule objects that are in the Rules section of the AuthorizationSection we have recovered. We will look to see if we find the following rules:

  • Deny access to all un-authenticated users
  • Deny access to all members of the SiteUsers role
  • Grant access to all members of the AdminUsers role

There is also another obstacle to overcome: we inherit access rules from the web.config that is the PublicFolder (the application's root folder). We need to identify these rules and ignore them since the access rules we are looking for will take precedence over any inherited rules. In general, settings in web.config files that are deeper in the hierarchy will overwrite settings from the application's root web.config. For access rules, deny rules take precedence over all grant rules. Thus if we grant access to un-authenticated users to all pages found in the PublicFolder and its subfolders, the fact that we add a rule denying all non-authenticated users access to the AdminFolder (which is a sub-folder of the PublicFolder) will take precedence.

To identify inherited rules we will inspect the ElementInformation for each of the AccessRules we iterate through. There are two such PropertyInformation objects that are of interest: "users" and "roles". An access rule can grant or deny access either to a specific user or to all the users in a specific role. Thus we can find rules that either apply to users or to roles and we need to get both PropertyInformation objects to be able to decide if we are dealing with the first or the latter of the two cases. For any given rule only one of these objects will contain a value, the other will be null. To decide if the property is inherited, we look at the ValueOrigin member of the PropertyInformation object and compare it to the PropertyValueOrigin.Inherited constant:

private bool IsRuleInherited(AuthorizationRule rule)
{
//to see if an access rule is inherited from the web.config above
//the current one in the hierarchy, we look at two PropertyInformation
//objects - one corresponding to roles and one corresponding to
//users

PropertyInformation
usersProperty = rule.ElementInformation.Properties["users"];
PropertyInformation rolesProperty = rule.ElementInformation.Properties["roles"];

//only one of these properties will be non null. If the property
//is equal to PropertyValueOrigin.Inherited, the this access rule
//if not returned in this web.config
if (usersProperty != null)
{
if (usersProperty.ValueOrigin == PropertyValueOrigin.Inherited)
return true;
}

if
(rolesProperty != null)
{
if (rolesProperty.ValueOrigin == PropertyValueOrigin.Inherited)
return true;
}

return
false;
}

Armed with this new method, we can no update the code that iterates though all the AuthorizationRules at this level of the application, adding the following logic: if a rule is inherited, ignore it since it is not one of the access rules that is of interest to us during the iteration – we can only change inherited rules in the web.config where they are inherited from – in the current web.config the only thing we can do is to overwrite their values.

foreach (AuthorizationRule ar in auth.Rules)
{
if (IsRuleInherited(ar))
{
continue;
}

//check if the rule is one of the rules that are of interest
//...
}


With this algorithm implemented, we can do the same for the web.config file that is found in the UsersFolder. To retrieve this file we will use the
WebConfigurationManager.OpenWebConfiguration(System.String) just like before, indicating the location of the config file starting from the root of our website ("/UsersFolder"). We then get a hold of the AuthorizationSection object and inspect its rules one by one ignoring any that are inherited:

Configuration config =
WebConfigurationManager.OpenWebConfiguration("/UsersFolder/");

AuthorizationSection
auth =
(AuthorizationSection)config.GetSection("system.web/authorization");

//check all rules in the AuthorizationSection of the web.config
//ignore the rules that are inherited and set the flags below if
//we find any of the rules that are of interest

bool
foundAllowSiteUsers = false;
bool foundDenyAnonymous = false;
bool foundAllowAdmins = false;

foreach
(AuthorizationRule ar in auth.Rules)
{
if (IsRuleInherited(ar))
{
continue;
}

//check if we are denying access to Un-Authenticated Users
//note that Un-Authenticated users are represented by the
// ? (question mark) sign

if
(ar.Users.Contains("?") &&
ar.Action == AuthorizationRuleAction.Deny)
{
foundDenyAnonymous = true;
continue;
}

//check if we are allowing access to the users that
//belong to the Administrators role
if (ar.Roles.Contains("Administrators") &&
ar.Action == AuthorizationRuleAction.Allow)
{
foundAllowAdmins = true;
continue;
}

//check if we are denying access to the users that
//belong to the SiteUsers role
if (ar.Roles.Contains("SiteUsers") &&
ar.Action == AuthorizationRuleAction.Allow)
{
foundAllowSiteUsers = true;
continue;
}
}

We are looking to have the following rules:

  • Deny access to all un-authenticated users
  • Grant access to users from the Administrators role
  • Grant access to users from the SiteUsers role

If these are not present we proceed to add them and save the new configuration:

//looking at the flags decide if we need to update the config
bool updateConfig = false;
AuthorizationRule accessRule;

if
(!foundAllowAdmins || !foundDenyAnonymous || !foundAllowSiteUsers)
{
//clear all pre-existing rules.
auth.Rules.Clear();

//create the rule - deny access to users belonging to the SiteUsers role
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Allow);
accessRule.Roles.Add("SiteUsers");
auth.Rules.Add(accessRule);

//create the rule - deny access to Un-Authenticated users
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Deny);
accessRule.Users.Add("?");
auth.Rules.Add(accessRule);

//create the rule - allow access to all users belonging to the Administrators role
accessRule =
new AuthorizationRule(AuthorizationRuleAction.Allow);
accessRule.Roles.Add("Administrators");
auth.Rules.Add(accessRule);

//set the update flag to true
updateConfig = true
}

if (updateConfig)
{
config.Save(ConfigurationSaveMode.Minimal);
}

Conclusion:

This shows you the steps that are needed to ensure that when your application starts up in IIS, you are able to check that the access rules you intended for Forms Authentication are in place and should they not be there, you can add them. A small note when deploying the sample in IIS: you will need to grant write access to the site.

This is needed so that you can save to disk the changes that you have made to the web.config files should you need to make any.

Source Code:

You may use the source code provided below. Just unzip the solution to a local folder on your computer and launch in Visual Studio 2008 or 2005

Monday, October 24, 2011

If your WCF service is unexpectedly receiving a null parameter value, try this…

An interesting problem ate up an hour and a half of my life yesterday morning. It was all to do with a WCF service.

The parameter that I was carefully formulating on one end of the wire and passing to my service proxy was not being received by my service at the other end - instead it was getting a null value. It worked the day before - why had it stopped working overnight?

After much head-scratching, I thought of checking the history of our Server project in the Source code repository. That gave me the clue I needed. In the file containing the interface which defines the service's contract, I noticed that the name of the parameter on the method in question had been changed.

Monday, October 17, 2011

Unable to translate Unicode character \uDBC0 at index to specified code page.

This error will come when we are doing some data encoding in HTML encoding in ASP.net. The following code is solution for the same...


string asAscii = Encoding.ASCII.GetString(
Encoding.Convert(
Encoding.UTF8,
Encoding.GetEncoding(
Encoding.ASCII.EncodingName,
new EncoderReplacementFallback(string.Empty),
new DecoderExceptionFallback()
),
Encoding.UTF8.GetBytes(line)
)

this might be save your time.....

Happy Coding..

Thanks,
Rajesh

Sunday, September 25, 2011

NHibernate Criteria using substring SqlFunction projection with in clause

I was playing with new NHibernate release (3.0.0 GA) and all of a sudden I felt an urge to check if one of bugs I have stumbled upon in 2.2 version is fixed. A quick example and few moments after - O happy day!!! This bug is fixed in 3.0.0 GA version.

Thank you awesome NHibernate developers.


P.S. Source code

01.using System;
02.using System.Collections.Generic;
03.using System.Reflection;
04.
05.using NHibernate;
06.using NHibernate.Cfg;
07.using NH3Tests.SimpleModel;
08.using NHibernate.Criterion;
09.
10.namespace NH3Tests
11.{
12.public class Program
13.{
14.static ISessionFactory factory;
15.
16.public static void Main(string[] args)
17.{
18.log4net.Config.XmlConfigurator.Configure();
19.
20.IList rooms = null;
21.using (ISession session = OpenSession())
22.{
23.ICriteria query = session.CreateCriterial;()
24..Add(Expression.In(
25.Projections.SqlFunction("substring",
26.NHibernateUtil.String,
27.Projections.Property("Code"),
28.Projections.Constant(1),
29.Projections.Constant(2)),
30.new string[] { "A1", "A2" }));
31.
32.rooms = query.List();
33.}
34.
35.Console.WriteLine("Done.");
36.}
37.
38.public static ISession OpenSession()
39.{
40.if (factory == null)
41.{
42.Configuration c = new Configuration();
43.c.AddAssembly(Assembly.GetCallingAssembly());
44.factory = c.BuildSessionFactory();
45.}
46.return factory.OpenSession();
47.}
48.}
49.}

POCO and mapping file:

01.using System;
02.using System.Collections.Generic;
03.
04.namespace NH3Tests.SimpleModel
05.{
06.public class Room
07.{
08.private int _id = 0;
09.private string _code = null;
10.private string _description = null;
11.
12.public Room()
13.{
14.}
15.
16.public int Id
17.{
18.get { return _id; }
19.set { _id = value; }
20.}
21.
22.public string Code
23.{
24.get { return _code; }
25.set { _code = value; }
26.}
27.
28.public string Description
29.{
30.get { return _description; }
31.set { _description = value; }
32.}
33.}
34.}
01.xml version="1.0" encoding="utf-8" ?>
02.<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true">
03.<class name="NH3Tests.SimpleModel.Room, NH3Tests" table="room" lazy="false">
04.<id name="Id" access="field.camelcase-underscore" column="room_id">
05.<generator class="native" />
06.id>
07.<property name="Code" access="field.camelcase-underscore" column="code"/>
08.<property name="Description" access="field.camelcase-underscore" column="description"/>
09.class>
10.hibernate-mapping>

And config:

01.xml version="1.0" encoding="utf-8" ?>
02.<configuration>
03.<configSections>
04.<section name="hibernate-configuration"
05.type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
06.<section name="log4net"
07.type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
08.configSections>
09.<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
10.<session-factory>
11.<property name="connection.provider">
12.NHibernate.Connection.DriverConnectionProvider
13.property>
14.<property name="connection.driver_class">
15.NHibernate.Driver.SqlClientDriver
16.property>
17.<property name="connection.connection_string">
18.Server=(local);database=NH3Tests;Integrated Security=SSPI;
19.property>
20.<property name="dialect">
21.NHibernate.Dialect.MsSql2008Dialect
22.property>
23.<property name="show_sql">trueproperty>
24.<property name='proxyfactory.factory_class'>NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFuproperty>
25.session-factory>
26.hibernate-configuration>
27.<log4net>
28.<appender name="ConsoleAppender"
29.type="log4net.Appender.ConsoleAppender, log4net">
30.<layout type="log4net.Layout.PatternLayout, log4net">
31.<param name="ConversionPattern" value="%m\n" />
32.layout>
33.appender>
34.<root>
35.<priority value="INFO" />
36.<appender-ref ref="ConsoleAppender" />
37.root>
38.log4net>
39.configuration>

And room table:
01.CREATE TABLE [dbo].[room](
02.[room_id] [int] IDENTITY(1,1) NOT NULL,
03.[code] [nvarchar](16) NOT NULL,
04.[description] [nvarchar](256) NULL,
05.CONSTRAINT [PK_room] PRIMARY KEY CLUSTERED
06.(
07.[room_id] ASC
08.)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
09.) ON [PRIMARY]

Tuesday, July 19, 2011

Can foreign key constraints be temporarily disabled using T-SQLin SQL Server.

I find it useful when populating data from one database to another. It is much better approach than dropping constraints. As you mentioned it comes handy when dropping all the data in the database and repopulating it (say in test environment).

-- disable all constraints
EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"

To switch them back on, run: (the print is optional of course and it is just listing the tables

- enable all constraints
exec sp_msforeachtable @command1="print '?'", @command2="ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all"

Also sometimes it is handy to disable all triggers as well,

sp_msforeachtable "ALTER TABLE ? DISABLE TRIGGER all"

exec sp_msforeachtable @command1="print '?'",
@command2="ALTER TABLE ? ENABLE TRIGGER all"