Authorize user based on model property ASP.NET MVC 4 - c#

I'm trying to prevent a user from being able to delete unless they created the model. (my models store the name of their creator) Right now a user clicks the "Delete" button which takes them to the delete page where they click the DeleteConfirmed button. How can I stop a user who hasn't created the model from deleting it?
I know I can get the current user's name with Context.User.Identity.Name
public ActionResult Delete(int? keyId)
{
Task mydata = db.MyDatas.Find(keyId);
...
return View(mydata);
}
[HttpPost, ActionName("Delete"), Authorize(Users = #"DOMAIN\Admin1 , DOMAIN\Bread")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int keyId)
{
Task mydata = db.MyDatas.Find(keyId);
...

Easiest method is to use SingleOrDefault instead of Find and query based on the user at the same time:
Task myData = db.MyDatas.SingleOrDefault(m => m.Id == keyId && m.CreatedBy == User.Identity.Name)
Then, a user will only get a non-null thing if they created it.

Two viable answers here:
MVC access restriction for logged in users
Either create a custom AuthorizationFilter to encapsulate that check/logic.
Or, you can build it into your db.MyDatas.Find(keyId);
That is, add something to the effect of:
&& Creator == Context.User.Identity.Name
(If it doesn't return an object then the current user doesn't have permission to delete it.)

You Can use ActionFilters thats what they are here for :
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if((string)filterContext.RouteData.Values["action"]=="Delete") //check if its delete action
{
if (db.MyDatas.Find(a=>a.ModelCreator.trim()==Context.User.Identity.Name.trim())==null)
{
Response.Redirect("~/Home"); //or whatever u want to give for unauthorized
}
}
}
Enjoy!

You will need to pass an instance of the model record that is going to be deleted into your cshtml, and then in your cshtml perform some basic logic like { if( Context.User.Identity.Name == model_var.user_that_created.Name){<a>Delete</a>}}

Related

Forbid access on page if form of another Action isn't submitted

I'm trying to create a registration in my ASP.NET MVC application. The registration is split in different areas.
First, I'm getting on the Create page. If the form in Create is successfully filled out and I submitted, I want to go on AccountInfo, which has another form.
[HttpGet]
public ActionResult Create()
{
return View("Create");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Account account)
{
if(ModelState.IsValid)
{
using (db)
{
return RedirectToAction("AccountInfo", new { account = account });
}
}
return View();
}
public ActionResult AccountInfo(Account account)
{
db.Accounts.Add(account);
db.SaveChanges();
return View();
}
How can I forbid the access on AccountInfo if the form in Create isn't filled out?
Well, all you care about is that account is filled in, so you really don't have to worry about whether or not someone came to AccountInfo directly. If they do, they won't have a valid Account object. If the Account Object is not valid, then you just redirect them back to Create.
Check that all the parameters in your first form are "valid" and filled in by the time you reach the second form. You can also check that the call came from that page. Just inspect the http request object to find the right info.
Here is the object I am referring to: http://msdn.microsoft.com/en-us/library/system.web.httprequest%28v=vs.110%29.aspx

How to redirect all pages in my mvc asp.net web app except for one role?

I work with asp.net c# mvc framework. I need a way to 'turn-off' my web app for all users except administrator (i. e. all pages should return to something like "The application is closed" for all the roles except Admin).
I already create a button in order to save the status of the web app (ON/OFF) in a DB.
Do I have to check on each page the status of the application ?
Is-it possible to have a global redirection except for one role ?
I don't know how to properly do this global closure. Any suggestions are welcomed.
I can think of three approaches to check and do a redircet
An HttpModule hooked into the appropriate, post-authorisation event. Presumably PostAuthorizeRequest of HttpApplication.
In your "global" (Global.aspx.cs) subscribe to that same event.
An MVC Action filter, overriding OnActionExecuting. (Ensure you make it global, to avoid needing to apply to every controller: add to GlobalFilters.Filters in your Application_Start.)
Of these 3 is part of MVC, but is much later in the pipeline (much more work will have been done, to be thrown away when the filter fails).
Use of a module is controlled by configuration which would make is easier to switch on and off.
option 2 is likely easiest to implement, but I would tend to prefer the modularity that 1 gives.
You can accomplish your requirement with the help of custom filters shown below :-
[CheckUserRole]
public class YourController : Controller
{
public ActionResult YourAction()
{
}
}
public class CheckUserRoleAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Get the User Id from the session
// Get Role associated with the user (probably from database)
// Get the permission associated with the role (like Read, write etc)
// if user is not authenticated then do as :
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "Error", action = "AccessDenied" }));
}
}
Did you tryActionFilterAttribute ?
Here is a basic example:
Your controller:
[IsAdmin]
public class YourController
{
}
Your attribute
public class IsAdminAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if () // Check that your user is not an Admin and that your application is "turn-off"
{
filterContext.Result = new HttpStatusCodeResult(403); // or whatever you want
}
}
}
Add [IsAdmin] on top of all your controllers.
You can write in all other Controllers which are used as follows..
public class HomeController : Controller
{
public ActionResult Index()
{
if (User.IsInRole("Administrator"))
return RedirectToAction("PagetoRedirect");
else
return RedirectToAction("CommonPagetoShowApplicationAsClosed");
}
}
Or
Action Filter, you can create on your own and look for named action like IndexRolename

Proper way to POST data MVC

Warning: This is my first web app.
I have 4 models, views and controllers. Lets call them A, B, C, D(ex. ModelA, ControllerA, ViewA). They are all basic views with list scaffolding.
/ControllerA/Index
User starts at ViewA and Selects an the first item, which redirects the user to ViewB
/ControllerB/Function?Aid=1
ViewB shows another list based on Selection from ViewA. Then the user Selects again is is redirected to ViewC
/ControllerC/Function?Aid=1&Bid=2
ViewC shows another list based on Selections from ViewA and ViewB. Then the user Selects again is is redirected to ViewD.
/ControllerD/Function?Aid=1&Bid=2&Cid=3
ViewD shows another list based on Selections from ViewA, ViewB, and ViewC, Then the user Selects again.
At this point I would like to POST Aid, Bid, Cid, and Did and save them in my database. Ideally the user would click the link, the data would be posted and then the site would redirect the user back to the homepage. Should I create another model and controller to Handle the post? I thought about trying to do the POST from controllerD but that doesn't seem like the proper way to do this.
The msdn tutorials only show posting directly from a view with a strongly typed model. I kinda stuck and I would prefer not to make this a complete mess.
Edit for Code
Controller
public ActionResult myFunction(int Aid = 0, int Bid, int Cid)
{
//query D stuff here
if (D == null)
{
return HttpNotFound();
}
return View(D.ToList());
}
[HttpPost]
[InitializeSimpleMembership]
public ActionResult CreateQuote(int Aid, int Bid, int Cid, int Did)
{
Quote myQuote = new Quote();
myQuote.Customer_ID_FK = (int)Membership.GetUser().ProviderUserKey;
myQuote.A_ID_FK = Aid;
myQuote.B_ID_FK = Bid;
myQuote.C_ID_FK = Cid;
myQuote.D_ID_FK = Did;
if (ModelState.IsValid)
{
db.Quotes.Add(myQuote);
db.SaveChanges();
db.Quotes.Max();
int mymax = db.Quotes.Max(q => q.ID);
return RedirectToAction();
}
return View(D.ToList());
}
[HttpPost]
[InitializeSimpleMembership]
public ActionResult CreateQuote(Quote myQuote)
{
myQuote.Customer_ID_FK = (int)Membership.GetUser().ProviderUserKey;
if (ModelState.IsValid)
{
db.Quotes.Max();
int mymax = db.Quotes.Max(q => q.ID);
db.Quotes.Add(myQuote);
db.SaveChanges();
return RedirectToAction();
}
return View(D.ToList());
}
It usually makes sense to put your post handler in the controller it's related to. This isn't always the case, as sometimes it would make more sense to make a new controller to handle all posts related to a certain task. You should also understand the distinction between a method IN a controller, and a controller. A controller is just a class that inherits from System.Web.Mvc.Controller and can have methods just like any other class. A perfectly reasonable controller could look like this:
public class DController : Controller
{
//GET /d
public ActionResult Index()
{
//MyModel is a class that would contain the logic to display
//the selections from A, B, and C
var model = new MyModel();
return View(model);
}
//POST /d/saveresults
//We only want this method to accept POST
[HttpPost]
public ActionResult SaveResults(MyEntity data)
{
var model = new MyModel();
model.SaveResultsToDatabase(data);
return Redirect("/");
}
}
The important thing in a controller is to keep logical processing to a minimum. There's nothing wrong with having an if statement here and there, but the majority of your logic should be handled by your model. A controller is there primarily to pass data between your views and models.

ViewModel update - changing properties before saving

After trying around the whole day with model bindings, without results, i decided to ask here.
I have got an asp.net razor view where a user (aka Seller) can edit his user details. Furthermore the user should be able to change his password.
I made a ViewModel:
public class EditSellerViewModel
{
public Seller Seller { get; set; }
public ChangePasswordModel ChangePasswordModel { get; set; }
}
My view has two forms which result in two "Submit" buttons. In my action i check which button was clicked. If the "Passwords" form has been submitted, i want to set the new Password in the Seller entity (that actually works) and SaveChanges() which does not change anything in the database (and does not throw any exception). It simply does nothing.
Furthermore if the "Seller Detail" form was submitted, i want to save the sellers data. But TryUpdateModel is always false, even if i use the second parameter which enables the prefix for ViewModels.
[HttpPost]
public ActionResult EditUser(string btnSubmit, FormCollection formValues, EditSellerViewModel editSellerViewModel)
{
int uid = baseFunc.GetIdForUsername(User.Identity.Name);
var seller = bmDBCont.SellerSet.Single(s => s.Id == uid);
if (btnSubmit == "saveSellerPassword")
{
seller.Password = editSellerViewModel.ChangePasswordModel.NewPassword;
bmDBCont.ObjectStateManager.ChangeObjectState(seller, System.Data.EntityState.Modified);
bmDBCont.SaveChanges(); //<-- does nothing
}
if (TryUpdateModel(seller, "Seller")) //<-- never true
{
bmDBCont.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Titles = CommonListsProvider.GetTitles();
ViewBag.Countries = CommonListsProvider.GetCountries();
return View(editSellerViewModel);
}
Here some debug info screenshots:
formcollection with seller form submitted
formcollection with password form submitted
Please can anyone help me?
See the documentation about TryUpdateModel, its says "Updates the specified model instance using values from the controller's current value provider and a prefix."
The Prefix to use when looking up values in the value provider.
Try use TryUpdateModel(seller) simple method without the "prefix" parameter.
if(TryUpdateModel(seller))
http://msdn.microsoft.com/en-us/library/dd493137(v=vs.108).aspx

Only allow access to action if redirected from specific action

Is there a good way to restrict the access to an action, so you can only access it, if you were redirected from another action. For example:
[HttpPost]
public virtual ActionResult Create(MyViewModel vm)
{
if (ModelState.IsValid)
{
// do some work
return RedirectToAction("CreateSuccess");
}
else
{
return View(vm);
}
}
public virtual ActionResult CreateSuccess()
{
// only allow execution if you were redirected from Action "Create"
}
An easy way would be to store a flag in TempData in the first method and check that the flag exists in the method that is redirected to.
TempData is there to pass state information between action requests and will only last the duration of the request so you will not need to worry about clearing it down.
There is no way to know the "from" action unless you include parameters indicating such. The easiest way is to append a "SourceAction" or "FromAction" parameter and check it in the "destination" action.
The question is, why do you want to do that? Maybe there is a better solution for your primary problem.
Anyway, you can use the HttpContext.Current.Request.UrlReferrer Property to check the previous page Url.
First solution
You could just do this:
[HttpPost]
public virtual ActionResult Create(MyViewModel vm)
{
if (ModelState.IsValid)
{
// do some work
return this.CreateSuccess();
}
else
{
return View(vm);
}
}
[NonAction]
public virtual ActionResult CreateSuccess()
{
// do what's needed
}
This last method will only be executed from other action methods. But it can't be executed per se.
Second solution
You could solve this by creating a custom action method selector attribute as well if you know you can reuse this. You could write a custom action method selector attribute that checks request referrer and uses appropriate method.
Read about custom action selector attributes.

Categories