NEW BLOG @ http://craigsdevspace.blogspot.com.au/2011/07/mvc3-security-fluent-way.html
In Asp MVC you can control user authorization by using security attributes to decorate the controller actions for authorization, for example, in the following AccountController class, the Authorize attribute decorates the ChangePassword action method so it will only allow logged in users to change their passwords. One way to test this is,
public void Verify_ChangePassword_Method_Is_Decorated_With_Authorize_Attribute()
{
var controller = new AccountController();
var type = controller.GetType();
var methodInfo = type.GetMethod("ChangePassword", new Type[] { typeof(ChangePasswordModel) });
var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true);
Assert.IsTrue(attributes.Any(), "No AuthorizeAttribute found on ChangePassword(ChangePasswordModel model) method");
}
While the above code certainly tests that the Change Password method has the Authorize Attribute the usage of Reflection is quite heavy. For me it makes the test a bit too verbose. Enter the Fluent Security framework. To quote the website “Fluent Security provides a fluent interface for configuring security in ASP.NET MVC. No attributes or nasty xml, just pure love.” Also available via NuGet:
PM> Install-Package FluentSecurity
Fluent Security enables the implementation of configuration based security. Let’s start by removing the Authorize attribute from the Account Controller:
public ActionResult ChangePassword(ChangePasswordModel model)
{
if (ModelState.IsValid)
{
// ChangePassword will throw an exception rather
// than return false in certain failure scenarios.
bool changePasswordSucceeded;
try
{
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
changePasswordSucceeded = currentUser.ChangePassword(model.OldPassword, model.NewPassword);
}
catch (Exception)
{
changePasswordSucceeded = false;
}
if (changePasswordSucceeded)
{
return RedirectToAction("ChangePasswordSuccess");
}
else
{
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Now to configure Fluent Security to secure the ChangePassword action, you can place the code the the Application start event of the Global.asax file. I prefer keep as much code out of that as possible and use a Application Bootstrapper.
{
public void Execute()
{
SecurityConfigurator.Configure(configuration =>
{
// Let Fluent Security know how to get the authentication status of the current user
configuration.GetAuthenticationStatusFrom(() => HttpContext.Current.User.Identity.IsAuthenticated);
// This is where you set up the policies you want Fluent Security to enforce
configuration.For<HomeController>().Ignore();
//configuration.For<AccountController>().DenyAuthenticatedAccess();
configuration.For<AccountController>(x => x.LogOff()).DenyAnonymousAccess();
});
GlobalFilters.Filters.Add(new HandleSecurityAttribute(), 0);
}
}
public interface IBootstrapThisApp
{
void Execute();
}
By default Fluent Security will throw an exception if a missing configuration is encountered for a controller action. If you don’t want Fluent Security to handle security for all controllers you can tell it to ignore missing configurations. You can do this by adding configuration.IgnoreMissingConfiguration(); to your configuration expression. Testing becomes pretty simple using Fluent Securities Test Helper, available as a NuGet package.
public void Should_Have_Correct_Security_Configuration()
{
new SecurityBootstrapper().Execute();
var results = SecurityConfiguration.Current.Verify(x =>
{
x.Expect<HomeController>().Has<IgnorePolicy>();
x.Expect<AccountController>().Has<DenyAuthenticatedAccessPolicy>();
x.Expect<AccountController>(y => y.LogOff()).Has<DenyAnonymousAccessPolicy>().DoesNotHave<DenyAuthenticatedAccessPolicy>();
});
Assert.That(results.Valid(), results.ErrorMessages());
}
I’ve only just started to implement security using the above methods and so far I am liking the framework. Testing becomes easier and it’s a lot easier to get an overview of your Web Applications Security configuration