I've got two controller actions at the moment: one that displays a strongly typed view, based on an unique identifier and another one that changes the model. Here's some code to visualize what I mean:
[HttpGet]
[ActionName("Edit")]
public ActionResult UpdateDevice(string code)
{
// Request the device based on the code.
var device = GetDeviceModel(code);
// Present the device in a view.
return View(device);
}
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
}
The code identifies the device, but it is also possible to change it. And that's my problem: Inside the post method I can access the new code using model.Code, but I also need to know the old code, to be able to change it.
I tried several alternatives, but none of them did satisfy my requirements:
ViewData does not get persisted until the post.
TempData is based on Sessions or Cookies – none of them I want to use at the moment.
Hidden fields and model bindings are not an option, because they can be manipulated on client side.
Finally I tried requesting data from the query string like this:
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
var oldCode = Request.QueryString["code"];
}
And this works! But I haven't found any resources on this around the web. So my question is: Is using the query string inside a post action safe against modifications? And what (if there are any) are the conditions for this to work?
If this is not a valid way to "remember" the code, are there any alternatives?
Based on the requirements you have mentioned in your question, it seems that you are looking for safety in the case. So I would say that QueryString is not safe. In my opinion using Session variables is the appropriate method that keeps your critical data in a safe location. You can use the method like this
[HttpGet]
[ActionName("Edit")]
public ActionResult UpdateDevice(string code)
{
Session["code"] = code;
....
}
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
if (Session["code"] == null)
throw new Exception("Error Message.");
var code = Session["code"].ToString();
....
Session["code"] = null;
}
I think you can make another property inside DeviceModel name OldCode.
and in your view you can save this value in hiddenfield like this
#Html.HiddenFor(m=>m.OldCode)
Now in controller post method you can get both the values like this.
[HttpPost]
[ActionName("Edit")]
public ActionResult UpdateDevice(DeviceModel model)
{
var oldcode=model.OldCode;
var newcode=model.Code;
}
Hope this helps...
Nothing submitted via a GET or a POST request is safe from modifications. While a GET query string is obviously easy to modify, it doesn't take much effort to spoof POST variables either. It sounds to me like you need to re-think your approach.
Optimally, you would do permission checking server-side to determine if the user is allowed to update the device with the passed (old) code. If they do not have permission, return an error such as an HTTP 550 code.
If you truly can't support this approach, I would suggest adding an "OldCode" field to the DeviceModel class as suggested by others, but encrypting it before sending it to the client. Then you can safely write it to a hidden field, and decrypt back on the server without fear of the user changing the value.
Related
Can I run these get and post actions with just single button? I get an id from my view then I just wanna run my post action.
I've already tried to getting id with parameter and put it in session but couldn't do it with one submit button
[HttpGet]
public ActionResult JoinEvent(int id)
{
Session["EventID"] = id;
return View();
}
[HttpPost]
public ActionResult JoinEvent(EventPerson eventPerson)
{
Person per =(Person) Session["Login"];
eventPerson.PersonID = per.PersonID;
eventPerson.EventID = (int)Session["EventID"];
eventPerson.TotalPerson = eventPerson.TotalPerson + 1;
eventManager.Join(eventPerson);
return View();
}
and this is my view
<td class="buy_link">
#Html.ActionLink("Join","JoinEvent","Join",item.EventID,null)
</td>
As what you posted here, it looks like the EventId can be set freely (without any security restrictions) by the client by simply navigating to GET /JoinEvent?id=100.
If security isn't an issue, you can do it by many many ways
Passing the event id to the client, which in turn will give it back to the server.
How ? Here is one way
Passing it as part of EventPerson model, which you will need to add a new property inside of that model.
ActionResult should: return View(new EventPerson { EventId: id })
Decorating view top row (after import statments) with the following #model EventPerson, this will tell the view that now he represents this type. (Which was passed by the GET action)
Now we should decide if we want to pass it back to the server as query string parameter or a body parameter ? Because we're doing a semantic "Insert" action, in most cases the practice asks us to pass it as part of the body. thus, you should use an hidden text box to pass EventId parameter.
#Html.Hidden("EventId", #model.EventId))
"EventId" is the property we want to bind on our POST server action EventPerson newly property. (Point#1)
#model.EventId is the value we took from the server and passed it into the View (Point#2)
Technically you cannot. When you create an anchor tag using action link, it creates a link to the specified URL. When you click on that link that fires a GET request. That will not POST data to the action method. If you need to POST data, you need to either submit a form or you can use Javascript/jQuery to do that. You can collect the data and send it as a POST using Js/jQuery. But just the anchor won't send a POST method.
Refer this
I have a link which uses a query string, and it's always going to the [HttpPost] method instead of the [HttpGet] method.
The error I'm getting is a NullReferenceException on TempData["surveytype"], so I know it's going to Post instead of Get. I have no idea why though.
I found a couple of similar questions here but nothing that resolved my problem. I thought maybe MVC was interpreting this as a form submission and sending to HttpPost because I'm styling the link as a "btn btn-primary" class, but removing that changed nothing.
My link:
Start Response
Controller:
[HttpGet]
public ActionResult Create(int SurveyId)
{
TempData["SurveyId"] = SurveyId;
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = ...)] Response response)
{
if (ModelState.IsValid)
{
response.Userid = User.Identity.GetUserId();
response.Id = Guid.NewGuid();
db.response.Add(response);
db.SaveChanges();
TempData["ResponseId"] = response.Id;
int? surveyid = response.SurveyId;
var surveytype = db.surveys.Find(surveyid).surveytype;
TempData["surveytype"] = surveytype;
...
}
}
I think your [HttpGet] makes the routing to confuse - Remove it
public ActionResult Create(int SurveyId)
{
TempData["SurveyId"] = SurveyId;
return View();
}
Now based on the action name the route will take care either it's a get or post - just a solution try it out and let me know if you face same issue
Thanks - Happy coding :)
For starters, you shouldn't manually create links like that. It's very error prone, and you run into situations like this often. You should use the helpers to create the link. For example:
#Html.ActionLink("Start Responses", "Create", "Controllername", new { SurveyId = item.id }, new { #class = "btn btn-primary" })
Next, you should make sure you don't have any custom routing that might be interfering, either with Attribute based routing or by using MapRoute.
The biggest reason to use the helpers is that MVC can access pages from different actual url's, and hard coding a path like that is almost impossible to get right.
For instance, let's say you can access a page at http://exmample.com, http://example.com/Home or http://example.com/Home/Index.
Using a hard coded "../whatever" means that this will translate to the corresponding url's http://example.com/../whatever (obviously not what you want), http://example.com/Home/../Whatever (might be what you want, might not), or http://example.com/Home/Index/../Whatever (this is probably what you want want, but it won't get there unless the users's browser url has this third url to access it).
If you must hard code a link, then you should always use a full root relative url link ("/Home/whatever") instead of relative ("../whatever"), but even that has issues.. what happens if you decide to move your site into a subdirectory of your site, now all your hard coded urls are wrong.
Using Url helpers is always the best way to go in MVC.
I somewhat understand how [ValidateAntiForgeryToken] prevents CSRF and I have read through this question, but I'm unsure whether this would prevent someone from falsifying the parameters for a form post.
ItemList has an Items property that is a collection of items and a User property that is a reference to the ApplicationUser that it belongs to. Item has an ItemList property that is a reference to the list it belongs to. Here are the Add methods in the ItemController:
// GET: Item/Add/4 (Adds new Item to the ItemList with ID=4)
public ActionResult Add(int? itemListId)
{
// Gets the current user and the ItemList that the Item will be added to
UserManager<ApplicationUser> userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db));
ApplicationUser currentUser = userManager.FindById(User.Identity.GetUserId());
ItemList itemList = db.ItemLists.Find(itemListId);
// Makes sure that ItemList exists and belongs to the user
if (itemList == null || itemList.User != currentUser)
{
return View("InsufficientPerm");
}
ViewBag.ItemListId = itemListId;
return View();
}
// POST: Item/Add/4 (Adds new Item to the ItemList with ID=4)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Add([Bind(Include = "ID,Name")] Item item, int? itemListId)
{
if (ModelState.IsValid)
{
ItemList itemList = db.ItemLists.Find(itemListId);
item.ItemList = itemList;
db.Items.Add(item);
itemList.Items.Add(item);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(item);
}
My question is whether [ValidateAntiForgeryToken] would prevent a user from falsifying the itemListId parameter during the post, or if I would need to put another if (itemList == null... check in the post method.
Edit: Here is the logic that I am looking at right now:
The use of ValidateAntiForgeryToken forces a user to access the first method (therefore loading the view) in order for a post to be accepted. If they don't load that view, then there will be no anti-forgery token.
The user will go to the form webpage (let's say http://foo.bar/Item/Add/3)
The user will fill out and submit the form, which would call the post method (in this case itemListId=3 because that's the webpage that was accessed)
There is no way for a user to pass a different itemListId to the above step, because it is passed by the webpage when they submit the form
Now, please let me know if there is something wrong with what I have stated above, or if that logic is correct (meaning that I don't need to check the validity of itemListId during the post). PLEASE give an example or a link to clarify where my logic is incorrect
There is no way for a user to pass a different itemListId to the above step, because it is passed by the webpage when they submit the form
This is false. The AntiForgeryToken will not protect you from altered data.
Make a GET request to Item/Add/4. I assume you will have a form on this page. You can include the #Html.AntiForgeryToken().
Use your browser's debug tools to inspect the form.
Now you can edit the action attribute to directly modify Item/Add/5 the value.
Or modify any <input> field value.
Put a debug break on your POST action and you will see the altered values when you submit the form.
The above is not the only way to tamper with the data. Therefore, you should always validate any input.
[Authorize, ValidateAntiForgeryToken]
[HttpPost]
public ActionResult NukeMyBankAccount(int accountId)
{
var account = db.GetAccount(accountId);
// validate
if (CurrentUser.Id != account.Owner.Id)
{
return RedirectToAction("Unauthorized");
}
else
{
db.NukeAccount(accountId, areYouSure: true);
}
...
}
No. The ValidateAntiForgeryToken attribute has absolutely zero impact on the values each form field may contain. It also has absolutely no idea what data belongs to which user.
ValidateAntiForgeryToken dont prevent falsify, it prevent that the request becomes from another place.
The ValidateAntiForgeryToken check if __RequestVerificationToken exists in the form and validate it, so whatever params is in the body of the request, if the __RequestVerificationToken is invalid will break and dont returns a 200 OK, so to use it just put the Helper #Html.AntiForgeryToken() inside the form and the [ValidateAntiForgeryToken] filter in the action.
So see this article about that: How AntiForgeryToken works
The CSRF attack consists in deceive victim to click in link or load a image where the URL is of a system that victim is logged, but attacker do not know the victim credentials.
The simple case, the attacker know that the HTTP GET request in the target system change some information. To attack, just send the e-mail or link to a user of the target system with a image where the URL point to the target system.
To prenvent this, a good pratice is do not create URL that make changes in data of the system through HTTP GET request.
In complex case, the attacker create a page with a form whose action point to a URL of the target system with POST method.
In this case, use the ValidateAntiForgeryToken is fundamental. Because the token generated by the target system is known only by the target system and can be validated.
So, the ValidateAntiForgeryToken only validate if the request was originated from the target system. Don't have any relationship with others parameters in the form.
So I've always used the loose PRG pattern where you return a view on ModelState validation failure from a POST action. However, it's always bothered me that I have to build the model not only in the GET action, but also rebuild it again in the POST action on failure. I've used different methods of doing the rebuilding using "view model builders" or just a private function within the controller that builds the view model for both actions, but these still bother me as well.
After reading this article by Ben Foster (http://benfoster.io/blog/automatic-modelstate-validation-in-aspnet-mvc), it makes a lot more sense to just rely on the GET action to build your view model - keeping it one area of the code - and then use the necessary action filters to save the ModelState for rendering when you are redirected back to GET on a failed POST.
So I've implemented that using the filters Ben mentions in his article like below. However, I am curious what happens if the user refreshes after they have been redirected back to the GET on a ModelState failure? How would I differentiate between someone accessing the GET directly versus a ModelState failure? Currently the ModelState would be gone if a user refreshes at that point. Is that the correct action, or should the user continue to see the errors until they POST with valid data? Essentially, should they be seeing the data that is in the database, or should they continue to see the changes they made when POSTing?
[ImportModelStateFromTempData]
public ActionResult Edit(int id)
{
// in a real application this would be retrieved from the db
var editView = new EditView()
{
UserId = id,
Name = "John Doe",
Age = 20,
Message = "Hello world"
};
return View(editView);
}
[HttpPost]
[ValidateModelState]
public ActionResult Edit(EditCommand editCommand)
{
// save to db here in real application
return RedirectToAction("Success");
}
I use the same [ImportModelStateFromTempData] filter in a couple projects, and it works great.
In my opinion, if the user refreshes, you shouldn't preserve any model state errors. The user is asking for a fresh view of that page, and it'd be frustrating to never be able to get a clean view. In same vain that refreshing after a POST shouldn't resubmit a form, refreshing after a GET shouldn't preserve a POST.
I have a requirement to change a form's action depending on what selection a user makes on a dropdown box inside the form, I have an MVC4/C# project on .NET 4.5
The choice that the user makes will influence which controller or which action needs to be called.
I have done a bit of searching around and I have come down to a few solutions:
Implement JQuery client side to dynamically change the form's action
Have the form go to a specific action dedicated to routing the request to the proper controller/action
From what I have read, people have recommended against using JQuery since you can't be sure the client is running javascript, and similar issues.
I have been playing around with my second option here, I have been trying to use RedirectToAction to perform the routing inside a switch block, however it won't work with POST data since redirecting will cause a GET.
An example of what I've attempted inside the controller follows:
[HttpPost]
public ActionResult Index(TestObject object)
{
switch (object.Type) {
case("A"):
return RedirectToAction("ActionA");
case ("B"):
return RedirectToAction("ActionB");
case ("C"):
return RedirectToAction("ActionC");
default:
ModelState.AddModelError("", "Invalid Type");
return View();
}
}
[HttpPost]
public ActionResult ActionA(TestObject object)
{
// Do Stuff for ActionA
return View();
}
This is the first MVC project I've created so I'm wondering what is considered the proper way if everyone keeps saying not to use JQuery for a situation like this.
If the second option is the 'proper' solution, how do I go about sending the data to the alternate controller actions?
You could do something like the following, i.e. pass form values as routevalues:
RedirectToAction("ACTIONNAME", "CONTROLLERNAME", ROUTEVALUES);
e.g.
this.RedirectToAction("displayimport", "coilenquiry", new { Number = number });
then in your action, use the parameters...
public ActionResult DisplayImport(string Number)
{
}
There's not currently any way to pass a model to RedirectToAction directly. This is what TempData is for:
TempData["MyModel"] = model;
switch (object.Type) {
case("A"):
return RedirectToAction("ActionA");
case ("B"):
return RedirectToAction("ActionB");
case ("C"):
return RedirectToAction("ActionC");
default:
ModelState.AddModelError("", "Invalid Type");
return View();
}
In your next action (note that this will be a GET request so you'll need to take the HttpPost annotation off it):
var model = TempData["MyModel"] as TestObject;
However, I'm a big fan of avoiding session storage wherever possible so, in this scenario, I wouldn't necessarily say that using jQuery is without any merit. The argument that "you can switch JavaScript off" is pretty moot nowadays given that the vast majority of modern sites are dependent on it.