Getting started
The bare essentials
Add the following at the end of Application_Start in your Global.asax and you're good to go!
The example is based on the Internet Application template for ASP.NET MVC 3.
SecurityConfigurator.Configure(configuration =>
{
// Let FluentSecurity 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 FluentSecurity to enforce on your controllers and actions
configuration.For<HomeController>().Ignore();
configuration.For<AccountController>().DenyAuthenticatedAccess();
configuration.For<AccountController>(x => x.ChangePassword()).DenyAnonymousAccess();
configuration.For<AccountController>(x => x.LogOff()).DenyAnonymousAccess();
});
GlobalFilters.Filters.Add(new HandleSecurityAttribute(), 0);
An extended example
The example is based on the Internet Application template for ASP.NET MVC 3 with the addition of a Blog controller and a security helper class.
SecurityConfigurator.Configure(configuration =>
{
// Let FluentSecurity know how to get the authentication status of the current user
configuration.GetAuthenticationStatusFrom(() => HttpContext.Current.User.Identity.IsAuthenticated);
// Let FluentSecurity know how to get the roles for the current user
configuration.GetRolesFrom(() => MySecurityHelper.GetCurrentUserRoles());
// This is where you set up the policies you want FluentSecurity to enforce
configuration.For<HomeController>().Ignore();
configuration.For<AccountController>().DenyAuthenticatedAccess();
configuration.For<AccountController>(x => x.ChangePassword()).DenyAnonymousAccess();
configuration.For<AccountController>(x => x.LogOff()).DenyAnonymousAccess();
configuration.For<BlogController>(x => x.Index()).Ignore();
configuration.For<BlogController>(x => x.AddPost()).RequireRole(BlogRole.Writer);
configuration.For<BlogController>(x => x.AddComment()).DenyAnonymousAccess();
configuration.For<BlogController>(x => x.DeleteComments()).RequireRole(BlogRole.Writer);
configuration.For<BlogController>(x => x.PublishPosts()).RequireRole(BlogRole.Owner);
});
GlobalFilters.Filters.Add(new HandleSecurityAttribute(), 0);
Policies
FluentSecurity provides a small set of policies that should be enough for the basic scenarios.
- DenyAnonymousAccessPolicy
- The user must be authenticated. Requires no specific role.
- DenyAuthenticatedAccessPolicy
- The user must be anonymous.
- RequireRolePolicy
- The user must be authenticated with one or more of the specified roles.
- IgnorePolicy
- All users are allowed.
Create your own policy
If needed, you can create your own policies by implementing the interface ISecurityPolicy. You can then add the policy to your configuration like this:
configuration.For<SomeController>().AddPolicy(new MyCustomPolicy());.
Configuration
Bootstrapping
The part that enforces your policies is the action filter called HandleSecurityAttribute. When you add it to your global filters collection, make sure that you put your call to SecurityConfigurator.Configure(...) before registering the action filter or it will blow up.
GlobalFilters.Filters.Add(new HandleSecurityAttribute(), 0);
Ignoring missing configurations
By default FluentSecurity will throw an exception if a missing configuration is encountered for a controller action. If you don't want FluentSecurity 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
The FluentSecurity.TestHelper project will help you get the configuration you want and enables you to testdrive your applicaiton like you would with any other part of your system. It can be used with any of the major testing frameworks (like NUnit, MbUnit, xUnit.net and MS Test).
To start writing tests for your FluentSecurity configuration you need to add a reference to FluentSecurity.TestHelper or just install the FluentSecurity.TestHelper NuGet package. Next you'll want to add a test fixture. Below is an example using NUnit.
[TestFixture] public class When_security_has_been_configured { [Test] public void Should_have_expected_configuration() { // Arrange Bootstrapper.ConfigureSecurity(); // Act var results = SecurityConfiguration.Current.Verify(expectations => { expectations.Expect<HomeController>().Has<IgnorePolicy>(); expectations.Expect<AccountController>().Has<DenyAuthenticatedAccessPolicy>(); expectations.Expect<AccountController>(x => x.LogOff()) .Has<DenyAnonymousAccessPolicy>() .DoesNotHave<DenyAuthenticatedAccessPolicy>(); }); // Assert Assert.That(results.Valid(), results.ErrorMessages()); } }
You need to make sure that FluentSecurity is configured before you can get to the current configuration so that is the first thing you'll do. In this case Bootstrapper.ConfigureSecurity() is just a wrapper around SecurityConfigurator.Configure(...) and could be named whatever you want. Putting your configuration in a bootstrapper class like this will make it easier for you to test FluentSecurity in isolation.
Now we can get to the current configuration using SecurityConfiguration.Current. Next we use the Verify(expectations => {}) extensions method passing in our expectations like shown in the above example.
The Verify extension returns an IEnumerable<ExpectationResult> with an expectation result for each expectation. To find out if our expectations are met we use the extension method Valid(). The extension method ErrorMessages() will give you a string representation of any expectations that has not been met.
Using your testing framework of choice you should assert that results.Valid() is true and provide results.ErrorMessages() as the failure message for that assertion.
