Code Snippet
[Test]
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");
}
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:
[sourcecode language="csharp"]
PM> Install-Package FluentSecurity
[/sourcecode]
Fluent Security enables the implementation of configuration based security. Let’s start by removing the Authorize attribute from the Account Controller:
Code Snippet
[HttpPost]
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);
}
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.
Code Snippet
public class SecurityBootstrapper : IBootstrapThisApp
{
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();
}
{
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.
Code Snippet
[Test]
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());
}
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
No comments:
Post a Comment