Tell Actions/Controllers to Persist TempData - asp.net-mvc

I understand that TempData is designed to work only between a single page request. But I think have a situation I think breaks the intended functionality.
I use Controllers in preference to Handlers for delivering images. I don't now if that is best practice, but it works very well for me. The problem though, is that each call to one of the image Actions obviously uses up a TempData credit.
Is there a way in MVC to say "This Controller/Action is out of the scope of normal page requests" and therefore either persist the TempData or remove itself from the TempData life cycle completely?
Rich

My solution has been to create an Attribute that persists the TempData across page requests. My initial reaction to this is "yuck", but I want to effectively exclude any controllers decorated with the Attribute from the TempData lifecycle.
using System.Web.Mvc;
namespace K3R.Web.Mvc.Filters {
public sealed class PersistTempDataAttribute : ActionFilterAttribute {
public PersistTempDataAttribute() { }
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var tempData = filterContext.Controller.TempData;
if (tempData == null || tempData.Count == 0)
return;
string[] keys = new string[tempData.Keys.Count];
tempData.Keys.CopyTo(keys, 0);
foreach (var key in keys)
tempData[key] = tempData[key];
}
}
}
Any feedback on a better solution would be appreciated.
Rich

I'm not sure of the exact answer to this as I'm pretty new to MVC myself. However, I have heard of people having trouble with AJAX requests killing off the TempData before they wanted it to as well.
I guess you could implement your own Session based system to store this information?
I'm sure someone else will have a more complete answer for you soon, though!

I do not completely understand your way of doing it and why but I would rather go with handlers. Or if you stick to controller actions, I can say that this worked for me fine without any issues for delivering images on-the-fly and I used FileContentResult in my controller actions.
Like this:
return File(imageBytes, imageType);
You get the bytes out of datastore or somewhere..
hope it helps

Related

Prevent Url Tampering to access another users data

I just wanted to gauge opinions on how I should approach this problem and ultimately looking for a quick win (wrong way to think about things nut time pressures mean I have to think and act quickly!
I've been given a website that has a bit of an issue.
I login using standard forms authentication as User1234 and my url is as follows:
www.mywebsite.co.uk/1234/Contact.
This will take me to User1234's details.
You can put two and two together and correctly assume that 1234 is a user id of some sort.
Once authenticated, I can access the views with [Authorize] attribute present, any anonymous/unathenticated users get redirected.
However, once logged in as User1234, I can then tinker with the url like so:
www.mywebsite.co.uk/1235/Contact.
So I am authenticated as User1234 but can see User1235's data. This is BAD for obvious reasons.
When I log in, I actively set the login ID in session so in theory, I could do a check whenever a user hits an ActionResult, I could cross check the ID present in the URL against the session login ID. However, it is a rather project with lots of action results and as such, I'm reluctant to spend my Saturday afternoon adding something to each and every ActionResult.
Is there an event in the global.asax I could use that is hit on each ActionResult request where I can compare Session login ID with url ID?
Alternatively, can anyone offer some suggestions about how I can achieve this or restrict URL tampering?
You can try and do a base controller
public class BaseController : Controller
{
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Do your stuff here
base.OnActionExecuted(filterContext);
}
}
I assume that you don't want to change your URL routes, as you could retrieve the user id also from the session. A quick solution would be to use an ActionFilter which you can place on the affected controllers or action methods:
public class VerifyUserIdAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var sessionUserId = filterContext.HttpContext.Session["UserId"];
var routeUserId = filterContext.RouteData.Values["UserId"];
if (routeUserId != null && sessionUserId == routeUserId)
filterContext.Result = new RedirectResult("<<url to redirect to>>");
}
}
I don't understand why the URL contains a data entry point. This appears to be a design flaw. I would remove all code that uses a URL parameter and instead make sure the controller looks up what the ID is based on the logged in user.

Designing MVC Crud with Modal problem (Not allow direct acess to Create action)

I´m have a Car View with a list of car... So, I have a Create button that opens a Modal (UI JQuery Dialog) with Site/Car/Create content...
All works fine... But I´d like to block direct access to : Site/Car/Create...
Is that possible? How?
Thanks
It's not really possible to block it completely, but you can do some things to make it more difficult. First, require that it come from a POST request. That will prevent someone from simply entering the URL with request parameters. Second, use the antiforgery token helper. That will help prevent a third-party from doing a POST to the url since they will also require both the token input and the token cookie. Third, you could potentially check if the request has the X-HTTP-REQUESTED-WITH header and only do the POST (or GET) via AJAX. It's not that hard to get around but it would prevent an accidental access if you do use GET. Fourth, and it probably should have been first, make sure that only authorized users have access to the action using the AuthorizeAttribute. Fifth, use SSL to prevent unauthorized access using FireSheep and protect your cookies and data from snooping.
In short, you won't be able to prevent a determined person with legitimate authorization from crafting a request to the action if they a really want to without using your interface. They can always craft a request that will look exactly like the one you would send. You can make it more difficult and prevent accidental access, though, using the above methods.
[AttributeUsage(AttributeTargets.Method)]
public class AjaxOnlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 404;
filterContext.Result = new HttpNotFoundResult();
}
else
{
base.OnActionExecuting(filterContext);
}
}
}
And then stick on your Create action like this:
[AjaxOnly]
public ActionResult Create()
{
...etc
}

ASP.NET MVC - State and Architecture

After a pair programming session, an interesting question came up which I think I know the answer for.
Question: Is there any other desired way in ASP.NET MVC to retain 'state' other than writing to database or a text file?
I'm going to define state here to mean that we have a collection of person objects, we create a new one, and go to another page, and expect to see the newly created person. (so no Ajax)
My thoughts are we don't want any kung-fu ViewState or other mechanisms, this framework is about going back to a stateless web.
What about user session? There are plenty of valid use cases to store things in session. And what about a distributed caching system like memcached? You also seem to leave out the query string - which is an excellent state saver (?page=2). To me those seem like other desirable methods to save state across requests...?
My thoughts are we don't want any kung-fu ViewState or other mechanisms, this framework is about going back to a stateless web.
The example you provided is pretty easy to do without any sort of "view state kung fu" using capabilities that are already in MVC. "User adds a person and sees that on the next screen." Let me code up a simple PersonController that does exactly what you want:
public ActionResult Add()
{
return View(new Person());
}
[HttpPost]
public ActionResult Add(PersonViewModel myNewPersonViewModel)
{
//validate, user entered everything correctly
if(!ModelState.IsValid)
return View();
//map model to my database/entity/domain object
var myNewPerson = new Person()
{
FirstName = myNewPersonViewModel.FirstName,
LastName = myNewPersonViewModel.LastName
}
// 1. maintains person state, sends the user to the next view in the chain
// using same action
if(MyDataLayer.Save(myNewPerson))
{
var persons = MyDataLayer.GetPersons();
persons.Add(myNewPersion);
return View("PersonGrid", persons);
}
//2. pass along the unique id of person to a different action or controller
//yes, another database call, but probably not a big deal
if(MyDataLayer.Save(myNewPerson))
return RedirecToAction("PersonGrid", ...etc pass the int as route value);
return View("PersonSaveError", myNewPersonViewModel);
}
Now, what I'm sensing is that you want person on yet another page after PersonSaveSuccess or something else. In that case, you probably want to use TempData[""] which is a single serving session and only saves state from one request to another or manage the traditional Session[""] yourself somehow.
What is confusing to me is you're probably going to the db to get all your persons anyway. If you save a person it should be in your persons collection in the next call to your GetPersons(). If you're not using Ajax, than what state are you trying to persist?
ASP.NET MVC offers a cleaner way of working with session storage using model binding. You can write a custom model binder that can supply instances from session to your action methods. Look it up.

ASP.NET MVC 1.0 Authentication

I realise that I can prevent unauthenticated users from accessing views at controller level by applying the [Authorize] attribute and can also filter views down to individual users or roles using this. However, my question is regarding doing the opposite... Is there a way to deny authenticated users from certain views without having to manually add in checks to see if they're authenticated in the opening lines of the controller code? Ideally an [Unauthorized] attribute or an equivalent if such a thing exists?
The reason for this is that I don't want authenticated users to be able to visit the account creation pages of the site I'm working on, as well as other resources. I realise I could check them in the controller explicitly but I'd prefer to decorate the controller methods if at all possible.
Thanks :)
This is along the lines of what LukLed was referring to:
public class UnAuthorizedAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool excludeCondition = false;
if (excludeCondition)
filterContext.Result = new HttpUnauthorizedResult();
else
base.OnAuthorization(filterContext);
}
}
Simply put in the logic for your excludeCondition. You can also to choose to do things like redirect to other views. Just mark your code with [UnAuthorized]
You can write your own authorization filter. Inherit from FilterAttribute and implement IAuthorizationFilter. Call it UnauthorizedAttibute and you will be able to use it like [Authorize].
Hear You can read about filters:
http://www.asp.net/LEARN/mvc/tutorial-14-cs.aspx
A simple way to accomplish this? Just leave the action untagged, and start with:
If(Request.IsAuthenticated)
// redirect somewhere, or return another view...
this could also be accomplished fairly simply if you are already using a roleprovider. then your actions would just need to be filtered by the appropriate role:
[Authorize(Roles = "Admin, Editor")]

Binding action parameters to request cookies in ASP.NET MVC - what happened?

In several early previews of ASP.NET MVC, arguments to controller methods would be resolved by inspecting the query string, then the form, then the cookies and server variables collections, as documented in this post from Stephen Walther.
For example, this code used to work:
public class MyController : Controller {
// This should bind to Request.Cookies["userId"].Value
public ActionResult Welcome(int userId) {
WebUser wu = WebUser.Load(userId);
ViewData["greeting"] = "Welcome, " + wu.Name;
return(View());
}
}
but now running against the release candidate, it throws an exception because it can't find a value for userId, even though userId definitely appears in the request cookies.
Was this change covered anywhere in the release notes? If this is a change to the framework, is there now a recommended alternative to binding cookies and server variables in this way?
EDIT: Thanks to those of you who have responded so far. I may have picked a bad example to demonstrate this; our code uses cookies for various forms of "convenient" but non-essential persistence (remembering ordering of search results, that kind of thing), so it's by no means purely an authentication issue. The security implications of relying on user cookies are well documented; I'm more interested in current recommendations for flexible, easily testable techniques for retrieving cookie values. (As I'm sure you can appreciate, the above example may have security implications, but is very, very easy to test!)
I believe it was the security implications that persuaded them to take these out:
The comments in Stephen Walther's post ASP.NET MVC Tip 15, leading to Phil Haack's posting User Input in Sheep's Clothing, especially his comment here:
#Troy - Step one is to dissuade devs from that line of thinking in the first place. ;) Step one prime (in parallel) is for us to remove the possibility of this line of thinking in this case.
The larger point still stands, we can make this change (after discussing it, we probably will), but that doesn't mean that it's suddenly safe to trust action method parameters.
Coupled with the complications of how you would call these methods from the various action builder classes.
I can't seem to find any explicit documentation one way or another about the controllers behaving like this other than Stephen's post, so I guess it was "quietly dropped".
I don't believe the cookies are checked anymore, and I'm not sure if it is intentional or not.
In an app against the RC I wrote recently I used the CookieContainer code from this post and a custom authorize attribute on classes like this:
public class LoginRequiredAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
IAppCookies a = new AppCookies(new CookieContainer());
return a.UserId != null; /* Better checks here */
}
}
My AppCookies.cs just has a method for UserId like this (auto resolves to int/null for you):
public int? UserId
{
get { return _cookieContainer.GetValue<int?>("UserId"); }
set { _cookieContainer.SetValue("UserId", value, DateTime.Now.AddDays(10)); }
}
Then just make sure your web.config is setup to point to your login page like this:
<authentication mode="Forms">
<forms loginUrl="~/Login"/>
</authentication>
This means in my controller to get a UserId I need to do something like this to retrieve my cookie:
[LoginRequiredAttribute]
public class RandomController : Controller
{
BaseDataContext dbContext = new BaseDataContext();
private readonly IAppCookies _cookies = new AppCookies(new CookieContainer());
public ActionResult Index()
{
return View(new RandomViewData(dbContext, _cookies.UserId));
}
}
Besides the obvious security implications, why would you need to pass the cookie through in the route anyway?
Surely you would be better off having an Authorize attribute on the action, indicating that the user should be authenticated before the action is executed.
At the end of the day, I think (hope) Microsoft has closed this as it's quite a large security issue. If this is the case, you should consider rewriting your application to comply with this.

Resources