I am writing an MVC 3 app where users will be able to log in and manage their data. I want to prevent users from viewing or tampering with other user's data. My first instinct was to just verify access to the relevant object in each action method like this:
public ActionResult ShowDetails(int objectId)
{
DetailObject detail = _repo.GetById(objectId);
if (detail.User.UserID != (Guid)Membership.GetUser().ProviderUserKey)
{
return RedirectToAction("LogOff", "Account");
}
}
This works fine, but I thought it might be better to put the object authorization code into a custom Authorize attribute derived from AuthorizeAttribute, which I could then apply to the controller. Unfortunately, I have not been able to find a way to access the action method parameters from within my custom Authorize attribute. Instead, the only way I have found to access the incoming objectId is by examining httpContext.Request or filterContext.RequestContext.RouteData.Values:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private int _objectId = 0;
private IUnitOfWork _unitOfWork;
public MyAuthorizeAttribute(IUnitOfWork uow)
{
_unitOfWork = uow;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
int.TryParse((string) filterContext.RequestContext.RouteData.Values["id"], out _objectId);
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
int objectId = 0;
if (httpContext.Request.Params.AllKeys.Contains("id", StringComparer.InvariantCultureIgnoreCase))
{
int.TryParse(httpContext.Request[idKey], out objectId);
}
if (objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
if (_objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
return base.AuthorizeCore(httpContext);
}
private bool IsAuthorized(int objectId, string userName)
{
DetailObject detail;
detail = _unitOfWork.ObjectRepository.GetById(objectId);
if (detail == null)
{
return false;
}
if (userName != detail.User.UserName)
{
return false;
}
return true;
}
}
I find this approach to be very clunky. I really don't want to have to poke around in the RouteData or Request objects; it would be much cleaner to be able to access the action method parameters since model binding would have already pulled out the relevant data from the RouteData and Request.
I know I can access action method parameters from a custom Action Filter (as detailed here), but shouldn't data authorization code be placed in an Authorize Filter? The more examples I see of Authorize filters, the more I get the impression that they are intended only to handle roles.
My main question is: How do I access action method parameters from my custom Authorize Attribute?
Answer to your main question: no, unfortunately AuthorizationContext does not provide access to action parameters.
First off, you could use ValueProvider to not have to deal with whether the id is part of the route or a query parameter or HTTP posted, as follows:
public override void OnAuthorization(AuthorizationContext filterContext)
{
string id = filterContext.Controller.ValueProvider.GetValue("id").AttemptedValue;
...
}
This works for simple data types and introduces little overhead. However once you start using custom model binders for your action parameters, you have to inherit your filter from ActionFilterAttribute to avoid double binding:
[MyFilter]
public ActionResult MyAction([ModelBinder(typeof(MyModelBinder))] MyModel model)
{
...
}
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = filterContext.ActionParameters["model"] as MyModel;
...
}
}
While semantically inheriting from AuthorizeAttribute for authorization purposes sounds better, there are no other reasons for doing this. Moreover, I find using ActionFilterAttribute easier, as all you have to do is override only one method, not keeping a state for subsequent methods.
Related
From what I can tell, ASP.Net Core performs model state validation before calling the relevant controller action method. This means that code in the action method isn't given an opportunity to add data to the model before it is validated.
What is the ASP.Net Core way of giving a view model access to additional, non-user-submitted data prior to validation?
Example
What I'm trying to do (doesn't work).
The view model's Validate method expects data to be in ValidOptions. However, since validation occurs before the controller can set this property, validation causes the view model to throw an ArgumentNullException.
// From the Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Process([Bind("SelectedId")]ViewModels.Import details)
{
// data needed for validation
details.ValidOptions = await service.ImportTypes.ToListAsync();
if (ModelState.ValidationState != ModelValidationState.Valid) {
// ...
}
}
// From ViewModels.Import
public IEnumerable<Option> ValidOptions { get; set; }
public int SelectdId {get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// throws ArgumentNullException because ValidOptions hasn't been set when this is executed
var option = ValidOptions.Single(t => t.Id == SelectdId);
//...
}
Probably many ways to skin a cat here. But the easiest for you is probably custom model binders. It's a way to "supplement" or change the binding of your model before it hits the controller. I will say that some see it as extremely bad practice to call an external service/repository at the point of model binding, but it does work and can come in handy.
You need to implement a class that inherits from IModelBinder.
public class MyViewModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
//Bind here. Including calling external services if you want.
}
}
Then you need to implement a provider, this essentially says "when" to bind.
public class MyViewModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(MyViewModel))
return new MyViewModelBinder();
return null;
}
}
In your configure method of your startup.cs, you need to add the provider to the ModelBinderProviders list.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc(config =>
config.ModelBinderProviders.Add(new MyViewModelBinderProvider())
);
}
Further Documentation :
http://dotnetcoretutorials.com/2016/12/28/custom-model-binders-asp-net-core/
http://intellitect.com/custom-model-binding-in-asp-net-core-1-0/
I don't think the official documentation has an article on custom model binders yet unfortunately.
I'm following a book called 'Asp.Net MVC4 in Action'. And now at certain point they say, Instead of relying on if statement within our code to check if the request is Ajax or not, we could use an action method selector to differentiate it. And what they have done is create a class AcceptAjaxAttribute with following code
using System;
using System.Reflection;
using System.Web.Mvc;
namespace CustomAjax
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AcceptAjaxAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
return controllerContext.HttpContext.Request.IsAjaxRequest();
}
}
}
and the function in controller which looked like this before
var speaker = _repository.FindSpeaker(id);
if(Request.IsAjaxRequest())
{
return Json(speaker, JsonRequestBehaviour.AllowGet);
}
return View();
has changed to something like this
[AcceptAjax]
public ActionResult Details(int id)
{
var speaker = _repository.FindId(id);
return Json(speaker, JsonRequestBehavior.AllowGet);
}
[ActionName("Details")]
public ActionResult Details_NonAjax(int id)
{
var speaker = _repository.FindId(id);
return View();
}
To be honest I have no idea what is being done or why we created new class and used that[AcceptAjax] thingy. Can someone may be explain it to me.
Before you had one action with an if inside, after the refactoring you have 2 actions each returning a different type of result. The ActionMethodSelectorAttribute is used to select the proper action based on some condition. Since the 2 actions have the same name (Details), ASP.NET MVC will use this attribute to select the one or the other based on whether an AJAX request is being used.
But honestly I don't like this approach at all. You now have 2 actions and have repeated the var speaker = _repository.FindId(id); call twice which is not very DRY. Wouldn't it be better if you had this?
[AcceptAjax]
public ActionResult Details(int id)
{
var speaker = _repository.FindId(id);
return View(speaker);
}
If you are like me and think that this is better, then simply replace this AcceptAjaxAttribute you got from the book with an action filter:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AcceptAjaxAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
var result = filterContext.Result as ViewResultBase;
if (result != null && result.Model != null)
{
filterContext.Result = new JsonResult
{
Data = result.Model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
}
}
The OnActionExecuted method will be invoked after the controller action has finished running and returned some result. Inside this method we verify whether the controller action returned a ViewResultBase (ViewResult or PartialViewResult) and whether a model has been passed. If this is the case we simply replace this result by a JsonResult.
And if you want to avoid decorating all your controller actions with this [AcceptAjax] attribute, you could register it as a global action filter in your ~/App_Start/FilterConfig.cs:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new AcceptAjaxAttribute());
}
}
Is it possible to block any other use of json result and allow just requests from my application ?
when we use something like this:
Json(q, JsonRequestBehavior.AllowGet)
it allow all requests from anywhere.is there any authentication exist to check where request is from ?
I think you mean:
How to allow only AJAX requests?
If so, view the following blog post. It describes creating a reusable filter:
AjaxOnly attribute
The code seems quite simple, but I haven't used it myself:
public class AjaxOnlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(!filterContext.HttpContext.Request.IsAjaxRequest())
filterContext.HttpContext.Response.Redirect("/error/404");
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
That you can then apply to controllers and actions:
[AjaxOnly]
public ActionResult AjaxActionMethod()
{
//....
}
The filter code presumes the existence of an action on some controller that can be reached by the following route:
/error/404
As a result, I have amended the code, and produced an easy way of adding an arbitrary error route (with a default value of "/error/404"):
public class AjaxOnlyAttribute : ActionFilterAttribute
{
public AjaxOnlyAttribute(){}
public AjaxOnlyAttribute(string ErrorRoute)
{
this.ErrorRoute = ErrorRoute;
}
string errorRoute = "/Error/404"; // default route
public string ErrorRoute
{
get { return errorRoute; }
set { errorRoute = value; }
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
filterContext.HttpContext.Response.Redirect(this.ErrorRoute); //
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
This can now be used as follows:
[AjaxOnly(ErrorRoute = "/MyArbitraryRoute/MyArbitraryParameter")
public ActionResult AjaxActionMethod()
{
//....
}
Add the [Authorize] attribute to your methods or controllers that you want to protect. You can specify the group membership and a login will be required.
If you only want a method to be callable by your own application, change the method declaration from public to internal. This will limit the scope of the method to calls from within your application.
I have defined a custom authorization attribute. In order to avoid listing the attribute above every ActionResult in my Controller(s), I’ve added the attribute to my global.asax as follows:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyAuthorizeAttribute());
}
An unintended consequence of this, however, is that my authorization code is getting called during all of my JsonResult calls as well. Is there a way for MyAuthorizeAttribute to get invoked ONLY on ActionResult calls and NOT on JsonResult calls? I'm using MVC3.
Does the filter run before or after the action method?
If it runs before, you can't know what concrete type will be returned, because all actions return ActionResult (unless you specifically return JsonResult - that's another story).
If it runs after the action method, then in your filter code check whether the result is a JsonResult, like that:
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if (!(filterContext.Result is JsonResult))
{
// do whatever you want...
}
base.OnResultExecuting(filterContext);
}
I disagree with the previous answer. You seem to have approached this from the wrong end. You should be looking at the request type, not the return type.
You can check for Ajax requests before the action in your filter. You should have something like this...
public class MyAuthorizeAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
return;
// else do authorisation stuff...
}
}
I have an ActionFilter with an override on the OnActionExecuted method. The filterContext.Controller.ViewData.Model is always null on a POST operation. I did find the following article that seems to be saying that it should not be null but this must have been an earlier version of MVC. This is MVC3. What should I be getting?
Model availability inside ActionFilter
UPDATE:
I've figured out the answer to the original question. I had a custom ActionResult that outputs JSON with a custom date formatter. The problem was that the model is not being set in the controller.
In my custom ActionResult the ExecuteResult method get passed the ControllerContext which would be nice if I could set the Model there:
context.Controller.ViewData.Model = _data;
But this is to late in the cycle and the result is still null in the ActionFilter. This seems to mean that I need to manually set the model in the controller:
ControllerContext.Controller.ViewData.Model = model;
Or
View(model);
Which then means I need to remember to do this every time I use this custom ActionResult. Is there a more elegant way?
YET ANOTHER UPDATE:
I found a way to do this it just isn't as elegant as I hoped.
In my constructor for the comstom ActionResult I sending in the controller, that way at least it will alway be consistent:
public JsonNetResult(object data, Controller controller) {
SerializerSettings = new JsonSerializerSettings();
_data = data;
controller.ControllerContext.Controller.ViewData.Model = _data;
}
Another approach is to use a base controller to automatically handle the storing of the action parameters collection for later use:
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Items["ActionParms"] = filterContext.ActionParameters.ToDictionary(p => p.Key, p => p.Value);
base.OnActionExecuting(filterContext);
}
}
then in your attribute:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var dictionary = filterContext.HttpContext.Items["ActionParms"] as Dictionary<string, object>;
if (dictionary != null)
{
foreach (var o in dictionary.Keys)
{
// do something here
}
}
base.OnActionExecuted(filterContext);
}
It uses HttpContext items which is not very nice but I don't know that you can access your ViewBag or ViewData in the attribute.
In order to decide whether you want to handle the request in your attribute, you can interrogate the action name and other parameter information:
var action = filterContext.ActionDescriptor.ActionName;
var parms = filterContext.ActionDescriptor.GetParameters();
foreach (var parameterDescriptor in parms)
{
// do something here
}
I found a solution like yours using the OnModelUpdated event to set that property before.
I have the ModelBinder:
public class CustomModelBinder: DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
controllerContext.Controller.ViewData.Model = bindingContext.Model;
base.OnModelUpdated(controllerContext, bindingContext);
}
}
After that, you need to set the default binder to your new model binder in Application_Start() section in Global.asax:
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
Finally you can access your Model in an ActionFilter:
public class TraceLog : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//filterContext.Controller.ViewData.Model now isn't null
base.OnActionExecuted(filterContext);
}
}