3

I have an application where some users belong to a Role, but may not actually have access to certain data within a URL. For instance the following url is open to all users

/Library/GetFile/1

However, some users may not have access to file1, but I can't use the Authorize attribute to detect that. I want instead to redirect those users to an unauthorized or accessdenied page. I'm using Forms Authentication and my config is set up like this

<authentication mode="Forms">
    <forms loginUrl="~/Home/Index" timeout="2880" />
</authentication>

my custom errors block is like this

<customErrors mode="On" defaultRedirect="Error" redirectMode="ResponseRewrite" >
    <error statusCode="401" redirect="Unauthorized"/>
</customErrors>

I am attempting to return the HttpUnauthorizedResult if the user does not have access, but I just get redirected to the login page, which isn't valid here because the User is Authenticated already.


It appears that the HttpUnauthorizedResult is setting the HTTP Response Code to 401 which Forms Authentication is hijacking and sending the user to the Login page.

Throwing the UnauthorizedAccessException doesn't seem to work either always redirecting the user to an IIS Error page even though I've updated my RegisterGlobalFilters to

filters.Add(new HandleErrorAttribute
{
    ExceptionType = typeof(UnauthorizedAccessException),
    View = "Unauthorized",
    Order = 3
});

If I change UnauthorizedAccessException to a custom Exception the redirect works and for now that's what I've done.

Steven
  • 860
  • 6
  • 24

2 Answers2

3

Your solution is similar to mine except that I did this:

  1. Create a custom exception, UnauthorizedDataAccessException.

  2. Create a custom exception filter (so that it could log the invalid access attempt).

  3. Register my custom exception attribute as a global filter in App_start.

  4. Create a marker interface, ISecureOwner and added it to my entity.

  5. Add a secure 'Load' extension method to my repository, which throws the exception if the current user is not the owner of the entity that was loaded. For this to work, entity has to implement ISecureOwner that returns the id of the user that saved the entity.

Note that this just shows a pattern: the details of how you implement GetSecureUser and what you use to retrieve data will vary. However, although this pattern is okay for a small app, it is a bit of hack, since that kind of security should be implemented deep down at the data level, using ownership groups in the database, which is another question :)

    public class UnauthorizedDataAccessException : Exception
    {
        // constructors
    }

    public class UnauthorizedDataAccessAttribute : HandleErrorAttribute
    {
        public override void OnException(ExceptionContext filterContext)
        {
            if (filterContext.Exception.GetType() == Typeof(UnauthorizedDataAccessException))
            {
                // log error
                filterContext.ExceptionHandled = true;
                filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Error", action = "UnauthorizedDataAccess" }));
            }
            else
            {
                base.OnException(filterContext);
            }
        }

    // marker interface for entity and extension method    
    public interface ISecureOwner
    {
        Guid OwnerId { get; }
    }

    // extension method
    public static T SecureFindOne<T>(this IRepository repository, Guid id) where T : class, ISecureOwner, new()
    {
        var user = GetSecureUser();
        T entity = repository.FindOne<T>(id);

        if (entity.OwnerId != user.GuidDatabaseId)
        {
            throw new UnauthorizedDataAccessException(string.Format("User id '{0}' attempted to access entity type {1}, id {2} but was not the owner. The real owner id is {3}.", user.GuidDatabaseId, typeof(T).Name, id, entity.OwnerId));
        }

        return entity;
    }

    // Register in global.asax
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        var filter = new UnauthorizedDataAccessAttribute { ExceptionType = typeof(UnauthorizedDataAccessException) };
        filters.Add(filter);
        filters.Add(new HandleErrorAttribute());
    }

   // Usage:
   var ownedThing = myRepository.SecureFindOne<myEntity>(id))
Rob Kent
  • 5,183
  • 4
  • 33
  • 54
  • In the end this is what I did except for 2 things. I didn't create a Custom HandleErrorAttribute just set the ExceptionType and Order on a second HandleError filter in my RegisterGlobalFilters method. I LIKE the idea of your 'Secure' extension on your repository! I may play with that idea some more. – Steven Dec 04 '11 at 15:05
  • @Steven, I was going to dispense with an attribute and just register the standard HandleError filter, but I noticed that you could only specify a View. Since I am using Areas, I need to be explicit about my controller. – Rob Kent Dec 05 '11 at 08:38
0

You can restrict access to certain roles. If an unauthorized role tries to access a resource you can redirect them to a specific url. Look at this other SO question: attribute-for-net-mvc-controller-action-method, there are good answers there. You can check in your code if a user belongs to a role:

User.IsInRole("RoleToTest");

you can also apply attributes to your controllers/action methods. Anyhow it is all explained in the link I specified above.

* EDIT * You could override OnException in your base Controller. Implement a custom exception, e.g., AccessNotAuthorizedAccessException. In OnExcepton, if you detect your custom exception, just redirect to a friendly url that shows the 'Not authorized...' message.

Community
  • 1
  • 1
santiagoIT
  • 9,411
  • 6
  • 46
  • 57
  • I understand how to check Roles AND use the Authorize attribute. The problem is that the user doesn't have access to the specific entity. I don't want to Redirect them to a View, I want to be able to throw an error or return the HttpUnauthorizedResult and have MVC handle it. – Steven Dec 02 '11 at 14:43
  • If you do not wish to redirect them to a view that says something like 'You are not authorized to view this content', what is it that you are expecting? I will post an updated answer which will give you a possible solution using a custom Authorize Attribute – santiagoIT Dec 02 '11 at 16:50