Unit Testing Secure Controller Actions with Moq

  • submit to reddit

Michael Ceranski is a .NET developer from Buffalo NY. He has been writing code for over 10 years starting with Borland Delphi and later migrating to the .NET stack. Michael enjoys blogging about .NET, MVC and jQuery. When Michael is not consulting he spends his time working on WeBlog, a next generation blogging platform written in ASP.NET MVC 2. Michael is a DZone MVB and is not an employee of DZone and has posted 28 posts at DZone. View Full User Profile

One of the hardest things to unit test in MVC is security. Security is tough to test because there is a lot of setup involved in mocking the HttpContext, the Principal and the Identity. For example, in WeBlog I am using the following code in the Edit Post action.
Post post = Repository.FirstOrDefault<Post>(x => x.ID == id);

if (post == null) return View("NotFound");
if (!HttpContext.User.CanEditPost(post)) return View("PermissionDenied");
In order to make sure this code works properly I need to test it with an authorized and unauthorized user. Unfortunately, the HttpContext.User will not automatically be created for your tests so you have to mock one for each test that your perform. So lets start this journey by reviewing the code required to mock the HttpContext using the popular opensource library Moq. This code is a combination of code I discovered on Stackoverflow and Scott Hanselman’s MvcMockHelpers:
public static Mock<HttpContextBase> MockContext( IPrincipal principal = null )
{
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();

context.Setup(ctx => ctx.Request).Returns(request.Object);
context.Setup(ctx => ctx.Response).Returns(response.Object);
context.Setup(ctx => ctx.Session).Returns(session.Object);
context.Setup(ctx => ctx.Server).Returns(server.Object);

if( principal != null )
context.Setup(ctx => ctx.User).Returns(principal);

return context;
}
If you are familiar with Scott’s MvcMockHelpers then you will probably notice that I modified the method signature to include an optional IPrincipal object. If the value is not null then I set the HttpContext.User. I also modified the SetFakeControllerContext extension method to take the IPrincipal parameter as well.
public static void SetFakeControllerContext(this Controller controller, IPrincipal principal = null )
{
var httpContext = FakeHttpContext( principal );
ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
controller.ControllerContext = context;
controller.Url = new UrlHelper( context.RequestContext, new RouteCollection() );
}
Now that we have the major pieces in place, its just a matter of creating a Fake user and passing it into the SetFakeControllerContext method. To create the IPrincipal, I am using these two Mock classes which were posted by Razzie on Stackoverflow:
public class MockPrincipal : IPrincipal
{
private IIdentity _identity;
private readonly string[] _roles;

public MockPrincipal(IIdentity identity, string[] roles)
{
_identity = identity;
_roles = roles;
}

public IIdentity Identity
{
get { return _identity; }
set { this._identity = value; }
}

public bool IsInRole(string role)
{
if (_roles == null)
return false;
return _roles.Contains(role);
}
}
public class MockIdentity : IIdentity
{
private readonly string _name;

public MockIdentity(string userName) {
_name = userName;
}

public override string AuthenticationType
{
get { throw new System.NotImplementedException(); }
}

public override bool IsAuthenticated
{
get { return !String.IsNullOrEmpty(_name); }
}

public override string Name
{
get { return _name; }
}
}

Putting the pieces together


So based on the code above I made two tests which create the Mock users and test their ability to edit a post. The first test should return the “PermissionDenied” View. Therefore, I create a new MockPrincipal which is in the Subscriber role. The Subscriber role is a read-only account which should prohibit the user from editing anything in the application.
[TestMethod]
public void EditActionShouldReturnPermissionDeniedViewForUnauthorizedUser() {
var principal = new MockPrincipal(new MockIdentity("Guest"), new string[] { Role.Subscriber });
var postController = new PostController(_repository);
postController.SetFakeControllerContext(principal);

var result = postController.Edit(_fakePost.ID) as ViewResult;
Assert.AreEqual("PermissionDenied", result.ViewName);
}
Next, we perform a similar test with an Admin user which should be able to edit any post.
[TestMethod]
public void EditActionShouldReturnValidViewForAuthorizedUser()
{
var principal = new MockPrincipal(new MockIdentity("Admin"), new string[] {Role.Admin});
var postController = new PostController(_repository);
postController.SetFakeControllerContext(principal);

var result = postController.Edit(_fakePost.ID) as ViewResult;
Assert.IsInstanceOfType(result.ViewData.Model, typeof(PostFormViewModel));
}
And the end result is a successful test!

References
AttachmentSize
image.axd_.png96.77 KB
0

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Hassan Turhal replied on Sun, 2012/01/22 - 12:29pm

Can't you just use the [Authorize(Roles = MyRole)] attribute on the function that actually edits the post, and therefore unit tests are redundant for security? Or am I missing something?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.