I've inherited a system which was written in MVC. This system uses the asp.net membership api, which works well. I've just discovered a bug however whereby the user is unable to change his/her password.
The system displays the form to enter in the old password, and the new password twice to confirm, however on clicking submit, it just redisplays the form, and does not change the password.
I've gone through the code but as I'm new enough to MVC, and using the membership api, I can't see anything overly wrong with it.
Here is the GET and POST code from within the Account Controller. If anyone could see anything wrong with this, I would greatly appreciate it. If anyone needs me to post additional information/code, please ask :)
Also, after debugging through the code, what seems to happen is, after
if (ModelState.IsValid)
is hit, the nested if statement within this is skipped and the code jumps straight down to the bottom to redisplay the form.
[Authorize]
public ActionResult ChangePassword(string source)
{
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
ViewData["source"] = source;
if (!string.IsNullOrEmpty(source))
{
return View("ChangePassword", source);
}
return View("ChangePassword", "User");
}
[Authorize]
[HttpPost]
public ActionResult ChangePassword(ChangePasswordModel model, FormCollection formValues)
{
string source = formValues["source"];
if (formValues["btnCancel"] != null)
{
RedirectToRouteResult result = null;
// The user has clicked cancel. Redirect back to source!
//
switch (source)
{
case "user":
result = RedirectToAction("Index", "ManageAccount", new { Area = "User" });
break;
case "administrator":
result = RedirectToAction("Index", "ManageAccount", new { Area = "Administrator" });
break;
}
return result;
}
if (ModelState.IsValid)
{
if (MembershipService.ChangePassword(User.Identity.Name, model.OldPassword, model.NewPassword))
{
return RedirectToAction("Index", "ManageAccount", new { Area = "User" });
}
else
{
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
}
// If we got this far, something failed, redisplay form
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
ViewData["source"] = source;
return View("ChangePassword", formValues["source"], model);
}
Maybe you could check for the Errors as stated here
ASP.NET MVC Model State
Related
I have little problem with my api and its account-management. Whenever a user creates a new account, it has to be confirmed by clicking on a link that is sent by email. That works perfectly fine.
However, when the user clicks on the link, it opens up a blank browser window. Now I'd like to redirect the user to a webpage that offers instructions for the application. After a bit of research, I found the "redirect(string url)" command.
[HttpGet]
[AllowAnonymous]
[Route("ConfirmEmail", Name = "ConfirmEmailRoute")]
public async Task<IHttpActionResult> ConfirmEmail(string userId = "", string code = "")
{
if (string.IsNullOrWhiteSpace(userId) || string.IsNullOrWhiteSpace(code))
{
ModelState.AddModelError("", "User Id and Code are required");
return BadRequest(ModelState);
}
IdentityResult result = await this.AppUserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
{
Redirect("http://www.orf.at");
return Ok();
}
else
{
return GetErrorResult(result);
}
}
The address should only be an example. Whenever one clicks on a link, however, the browser still opens a blank tab. Is there a possibility to avoid that? The best solution would be to redirect the user to another site. But if that does not work, can it somehow be prevented that a new window is opened up?
Instead of returning Ok() return Redirect("http://www.orf.at");
if (result.Succeeded)
{
return Redirect("http://www.orf.at");
}
else
{
return GetErrorResult(result);
}
I'm working on an MVC5 application. On the home screen is a grid allowing users to view Data and be transferred to a number of Views for various actions on each record. One of these is an [EDIT].
The issue I'm encountering is as follows: due to the amount of data it is convenient to Filter the data down (say to a specific location) and then Edit records from there. The filter on this grid (Grid.MVC from CodePlex) performs filtering partially by modifying the URL (http://homeURL/?grid-filter=Location.DEPT__1__accounting) such as 1 being Equals, 2 being Cotains, 3 being StartsWith, and 4 being EndsWith and then after the next 2 underscores being the search criteria.
This functions fine, however upon [POST] return from the Edit the user currently is returned to main Index view without the filtering criteria still set (forcing them to go in over and over and add filtering criteria before performing the similar EDIT on records of the same criteria).
My POST-EDIT method is currently setup to include:
if (ModelState.IsValid)
{
collection.MODIFIED_DATE = DateTime.Now;
collection.MODIFIED_BY = System.Environment.UserName;
db.Entry(collection).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index", "Home");
}
For my attempts I had first thought to return the View with the updated collection (return View(collection)) but this of course just takes me back to the EDIT view, not the home view with the data grid filtered down as previously specified. I considered adding a field in the database, something like LAST_FILTERED_URL, but this just feels like an overgrown band-aid.
Does anyone know of a clean way to go about this?
EDIT:
I had thought to do something similar to Andrea's suggestion early on, but had not thought of doing an explicit redirect with the Parameter of the url-filter passed in the Redirect. Below is my current code for the GET/POST Edit:
// GET: ENITTY_Collection/Edit/5
public async Task<ActionResult> Edit(int id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
ENTITY_COLLECTION entity_Collection = await db.ENTITY_COLLECTION.FindAsync(id);
if (entity_Collection == null)
{
return HttpNotFound();
}
// Other code for Controls on the View
return View(entity_Collection);
}
// POST: ENTITY_Collection/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,One_Id,Two_Id,Three_Id,Four_Id,Five_Id,Six_Id,field7,field8,field9,...field18,created_date,created_by,modified_date,modified_by")] ENTITY_COLLECTION entity_Collection)
{
if (ModelState.IsValid)
{
entity_Collection.MODIFIED_DATE = DateTime.Now;
entity_Collection.MODIFIED_BY = System.Environment.UserName;
db.Entry(entity_Collection).State = EntityState.Modified;
await db.SaveChangesAsync();
//return RedirectToAction("Index", "Home");
return View(entity_Collection);
}
// Other code for if Model is Invalid before returning to View.
return View(entity_Collection);
}
I like Andrea's suggestion, but I still need a good way to store the URL the user has when they first navigate to the GET-Edit View, and then use that filtered URL value to return the user to that previous location & filter option when the POST-Edit completes and changes have saved.
Any thoughts?
I'm not sure if this is the most correct way of going about what I'm after, but what appears to be working for me is the use of a Session value.
In my GET method I store the URL:
Session["returnURL"] = Request.UrlReferrer.AbsoluteUri;
Then in my POST I use this value in a Redirect() after saving changes to the record:
var returnURL = (Session["returnURL"] != null) ? Session["returnURL"].ToString() : Url.Action("Index", "Home");
return Redirect(returnURL);
So far all initial testing is resulting in a return to the main view with all sorting/filtering criteria in place before the record was entered into for update.
Have you tried changing passing the current filter to redirect to action as follows?
Note: I am assuming that:
you are redirecting to the same controller
you have a controller parameter called currentFilterValue
RedirectToAction("Index", "Home",new { grid-filter = currentFilterValue });
The default LoginController for an MVC project from Microsoft includes a bunch of methods that use a returnUrl parameter. Using this practice, you could include a return URL when opening the editor, that when editing is done, returns the user back to the prior screen with the filters intact (assuming they are in the URL).
My base class for view models has a property for storing the ReturnURL, which is then stored as a hidden if set.
#Html.HiddenFor(model => model.ReturnUrl)
My action that posts from edit then has this logic:
if (viewModel.ReturnUrl.IsNotNullOrEmptyTrimmed())
{
return RedirectToLocal(viewModel.ReturnUrl, "ActionName", "ControllerName");
}
else
{
// Default hard coded redirect
}
In order to prevent some injections, you will want a validation method (called above) like this for ensuring the URL is valid:
protected ActionResult RedirectToLocal(string returnUrl, string defaultAction, string defaultController)
{
try
{
if (returnUrl.IsNullOrEmptyTrimmed())
return RedirectToAction(defaultAction, defaultController);
if (Url.IsLocalUrl(returnUrl))
return Redirect(returnUrl);
Uri returnUri = new Uri(returnUrl);
if (Url.IsLocalUrl(returnUri.AbsolutePath))
{
return Redirect(returnUrl);
}
}
catch
{
}
return RedirectToAction(defaultAction, defaultController);
}
Hopefully this gets you some ideas and on the right track.
MVC5 is storing my passwords in plaintext. I don't want to use the default hashing algorithm, as I'm required to use e.Encrypt() instead. I'm creating a registration function, and I need to know how I can edit values from FormCollection before using TryUpdateModel.
Here's the code:
[HttpPost]
public ActionResult Register([Bind(Include = "User,Pass,Email")] FormCollection form)
{
var user = new Users();
string Hash = e.Encrypt(form["Pass"]); // Gets set.
if (TryUpdateModel(user, form))
{
context.Entry(user).State = EntityState.Added;
context.SaveChanges();
return RedirectToAction("Login", "Account");
}
return View();
}
I've searched high and low, and everything I've found is irrelevant to my needs.
I've tried this:
form["Password"] = e.Encrypt(form["Password"])
...and that compiles, but when debugging, the value never gets set. e.Encrypt() does work as a function, so it's not that.
What am I doing wrong?
I figured it out after some trial and error:
[HttpPost]
// Remove FormCollection and replace with UserModel.
public ActionResult Register([Bind(Include= "Username,Password,EmailAddress")] UserModel user)
{
if (TryUpdateModel(user))
{
// Set password in TryUpdate success.
user.Password = e.Encrypt(user.Password);; // Set correctly
context.Entry(user).State = EntityState.Added;
context.SaveChanges();
return RedirectToAction("Login", "Account");
}
return View();
}
However, another issue popped up, DataValidationError. The issue is from the UserModel.cs class:
[RegularExpression(RegexPassword, ErrorMessage = ErrorPassword)]
I had a regular expression which didn't match the hash, so when I tried to update, it was unable to validate. That's a problem for another thread, but I just removed it for now.
I have an asp.net mvc4 application, in which i have to logout from an account :
if (_fonction == "User")
{
if (_is_admin == true) return RedirectToAction("Index");
else
{
Session["user"] = _u;
return RedirectToAction("Index", "User");
}
}
in the controller User
public ActionResult Index()
{
if (Session["user"] == null) return RedirectToAction("Index", "Home");
return View(Session["user"]);
}
the action Logout
public ActionResult Logout()
{
if (_is_admin) { Session["user"] = null; return RedirectToRoute("Administration"); }
else { Session["user"] = null; return RedirectToAction("Index", "Home"); }
}
i do this : i log in to a user account then i disconnect so i'am in the home page , then i click into the back button of the browser i got the page of the account. When i refresh i returned to the home page. i think that the problem is in the cache and i don't think that make it null is a good idea .
So how can i fix this problem?
You can try to add [OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")] as an attribute to your User controller's Index action to force non-cached results.
But I would strongly suggest to just use AuthorizeAttribute because this will prevent unauthorized web requests done on a specific view. The benefit of using this is that you still give the users the liberty to cache your views and be secured at the same time.
if you do not want to clean your cache then below is the javascript which helps you to hard refresh your page on click of the browser back button
if (window.name != "") { // will be '' if page not prev loaded
window.name = ""; // reset to prevent infinite loop
window.location.reload(true);
}
window.name = new Date().getTime();
put the above "javascript" code on your page. so, it will hard refresh your page.
Ive got a problem: my websecurity always throws false on confirmation. What am I doing wrong?
Here is my Validate Action(Ive debugged it, the id received is the right confirmation token:
public ActionResult Validate(String Id)
{
if (String.IsNullOrEmpty(Id))
{
return View();
}
bool b = WebSecurity.ConfirmAccount(Id);
if (b)
{
return View("ConfirmationSuccess");
}
return View("ConfirmationFailure");
}
And here is my registration action:
public ActionResult Register(RegisterModel model, string ReturnUrl)
{
if (ModelState.IsValid)
{
// Попытка зарегистрировать пользователя
try
{
string confirmationToken = WebSecurity.CreateUserAndAccount(model.rEmail.ToLower(), model.rPassword, null, true);
dynamic email = new Email("~/Views/Emails/RegisterConfirmation.cshtml");
email.To = model.rEmail;
email.ConfirmationToken = confirmationToken;
email.Send();
return RedirectToAction("EmailValidation", new { Email = model.rEmail.ToLower() });
}
catch (MembershipCreateUserException e)
{
string field = string.Empty;
switch (e.StatusCode)
{
case MembershipCreateStatus.DuplicateUserName:
field = "rEmail";
break;
case MembershipCreateStatus.InvalidPassword:
field = "rPassword";
break;
default:
field = "RegisterForm";
break;
}
ModelState.AddModelError(field, ErrorCodeToString(e.StatusCode));
}
}
ViewBag.RegisterModel = model;
ViewBag.ReturnUrl = ReturnUrl;
ViewBag.LoginModel = new LoginModel();
//ModelState.AddModelError("rEmail", "Пользователь с таким e-mail уже зарегистрирован");
// Появление этого сообщения означает наличие ошибки; повторное отображение формы
return View("Login");
}
after registration the email is sent, the link is the right link, but when It goes to WebSecurity.ConfirmAccount(Id), it always throws false....
Thank you for your time and sorry for my bad English.
UPD:
There is a converter for all urls to lower on my IIS server. Could it be the case, that it compares keys case-sensitive? And how can I fix this?
UPD:
Ok, the problem is really in lowercase url. WebSecurity.ConfirmAccount is case-sensitive... Ive made a little workaround in my action so that I could get the right ConfitmtionToken, but this is not fully right way, while there could be two identical ConfirmationToken.ToLower() as I think, So, please, somebody, point me the right way to do it.
And here is my workaround:
public ActionResult Validate(String Id)
{
if (String.IsNullOrEmpty(Id))
{
return View();
}
//bool b = WebSecurity.ConfirmAccount(Id);
using (var ctx = new DBContext())
{
Id = ctx.wpMembership.Where(s => s.ConfirmationToken.Equals(Id, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault().ConfirmationToken;
}
if (WebSecurity.ConfirmAccount(Id))
{
return View("ConfirmationSuccess");
}
return View("ConfirmationFailure");
}
This is because you are passing the account Id not the confirmation token. You need to pass the confirmation token.
The confirmation token is generated by the CreateAccount(String, String, Boolean), CreateUserAndAccount(String, String, Object, Boolean), and GeneratePasswordResetToken(String, Int32) methods. The token is ordinarily sent to the user as a link in an email message, and the user clicks the link in order to verify his or her identity.