I'm implementing CAPTCHA in my form submission as per Sanderson's book Pro ASP.NET MVC Framework.
The view fields are generated with:
<%= Html.Captcha("testCaptcha")%>
<%= Html.TextBox("attemptCaptcha")%>
The VerifyAndExpireSolution helper is not working as his solution is implemented.
I'm adding validation and when it fails I add a ModelState error message and send the user back to the view as stated in the book:
return ModelState.IsValid ? View("Completed", appt) : View();
But, doing so, generates a new GUID which generates new CAPTCHA text.
The problem is, however, that the CAPTCHA hidden field value and the CAPTCHA image url both retain the original GUID. So, you'll never be able to enter the correct value. You basically only have one shot to get it right.
I'm new to all of this, but it has something to do with the view retaining the values from the first page load.
Captcha is generated with:
public static string Captcha(this HtmlHelper html, string name)
{
// Pick a GUID to represent this challenge
string challengeGuid = Guid.NewGuid().ToString();
// Generate and store a random solution text
var session = html.ViewContext.HttpContext.Session;
session[SessionKeyPrefix + challengeGuid] = MakeRandomSolution();
// Render an <IMG> tag for the distorted text,
// plus a hidden field to contain the challenge GUID
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
string url = urlHelper.Action("Render", "CaptchaImage", new{challengeGuid});
return string.Format(ImgFormat, url) + html.Hidden(name, challengeGuid);
}
And then I try to validate it with:
public static bool VerifyAndExpireSolution(HttpContextBase context,
string challengeGuid,
string attemptedSolution)
{
// Immediately remove the solution from Session to prevent replay attacks
string solution = (string)context.Session[SessionKeyPrefix + challengeGuid];
context.Session.Remove(SessionKeyPrefix + challengeGuid);
return ((solution != null) && (attemptedSolution == solution));
}
What about re-building the target field names with the guid? Then, each field is unique and won't retain the previous form generations' value?
Or do I just need a different CAPTCHA implementation?
I had the same problem using the captcha example from Sanderson's book. The problem is that the page gets cached by the browser and doesn't refresh after the captcha test fails. So it always shows the same image, even though a new captcha has been generated and stored for testing.
One solution is to force the browser to refresh the page when reloading after a failed attempt; this won't happen if you just return View(). You can do this using RedirectToAction("SubmitEssay") which will hit the action method accepting HttpVerbs.Get.
Of course, you lose the ability to use ViewData to notify your user of the error, but you can just include this in the query string, then just check the query string to display your message.
So, following the book's example,
if (!CaptchaHelper.VerifyAndExpireSolution(HttpContext, captcha, captchaAttempt)
{
RedirectToAction("SubmitEssay", new { fail = 1 });
}
Then just check if the QueryString collection contains 'fail' to deliver your error message.
So, I decided to implement reCaptcha. And I've customized my view likewise:
<div id="recaptcha_image"></div>
<a href="#" onclick="Recaptcha.reload();">
generate a new image
</a><br />
<input type="text" name="recaptcha_response_field"
id="recaptcha_response_field" />
<%= Html.ValidationMessage("attemptCaptcha")%>
<script type="text/javascript"
src="http://api.recaptcha.net/challenge?k=[my public key]"></script>
This creates two captchas- one in my image container, and another created by the script. So, I added css to hide the auto-generated one:
<style type="text/css">
#recaptcha_widget_div {display:none;}
</style>
Then, in my controller, I merely have to test for captchaValid:
[CaptchaValidator]
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult SubmitEssay(Essay essay, bool acceptsTerms, bool captchaValid)
{
if (!acceptsTerms)
ModelState.AddModelError("acceptsTerms",
"You must accept the terms and conditions.");
else
{
try
{
// save/validate the essay
var errors = essay.GetRuleViolations(captchaValid);
if (errors.Count > 0)
throw new RuleException(errors);
}
catch (RuleException ex)
{
ex.CopyToModelState(ModelState, "essay");
}
}
return ModelState.IsValid ? View("Completed", essay) : View();
}
public NameValueCollection GetRuleViolations(bool captchaValid)
{
var errors = new NameValueCollection();
if (!captchaValid)
errors.Add("attemptCaptcha",
"Please enter the correct verification text before submitting.");
// continue with other fields....
}
And all of this assumes that you've implemented the Action Filter attribute and the view helper as detailed at recaptcha.net:
public class CaptchaValidatorAttribute : ActionFilterAttribute
{
private const string CHALLENGE_FIELD_KEY = "recaptcha_challenge_field";
private const string RESPONSE_FIELD_KEY = "recaptcha_response_field";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var captchaChallengeValue =
filterContext.HttpContext.Request.Form[CHALLENGE_FIELD_KEY];
var captchaResponseValue =
filterContext.HttpContext.Request.Form[RESPONSE_FIELD_KEY];
var captchaValidtor = new Recaptcha.RecaptchaValidator
{
PrivateKey = "[my private key]",
RemoteIP = filterContext.HttpContext.Request.UserHostAddress,
Challenge = captchaChallengeValue,
Response = captchaResponseValue
};
var recaptchaResponse = captchaValidtor.Validate();
// this will push the result value into a parameter in our Action
filterContext.ActionParameters["captchaValid"] = recaptchaResponse.IsValid;
base.OnActionExecuting(filterContext);
}
}
html helper:
public static class Captcha
{
public static string GenerateCaptcha( this HtmlHelper helper )
{
var captchaControl = new Recaptcha.RecaptchaControl
{
ID = "recaptcha",
Theme = "clean",
PublicKey = "[my public key]",
PrivateKey = "[ my private key ]"
};
var htmlWriter = new HtmlTextWriter( new StringWriter() );
captchaControl.RenderControl(htmlWriter);
return htmlWriter.InnerWriter.ToString();
}
}
Hope this helps someone who got stuck with the implementation in the book.
Related
I'm working on an ASP.NET MVC Website that uses FluentSecurity to configure authorizations. Now we need a custom ActionLink Helper which will be displayed only if the current user has access to the targeted action.
I want to know if there is a way to know dynamically, from FluentSecurity Configuration (using SecurityConfiguration class for example), if the current logged user has access to an action given her name (string) and her controller name (string). I spend a lot of time looking in the source code of FluentSecurity https://github.com/kristofferahl/FluentSecurity but with no success.
For example:
public bool HasAccess(string controllerName, string actionName) {
//code I'm looking for goes here
}
Finally, i will answer myself may this will help another one.
I just emulate the OnAuthorization method of the HandleSecurityAttribute class. this code works well:
public static bool HasAccess(string fullControllerName, string actionName)
{
ISecurityContext contx = SecurityContext.Current;
contx.Data.RouteValues = new RouteValueDictionary();
var handler = new SecurityHandler();
try
{
var result = handler.HandleSecurityFor(fullControllerName, actionName, contx);
return (result == null);
} catch (PolicyViolationException)
{
return false;
}
}
In my MVC 5 app I need to be able to dynamically construct a list of fully qualified external URL hyperlinks, alone with some additional data, which will come from the Model passed in. I figure - I will need to construct my anchor tags something like this:
{{linkDisplayName}}
with AngularJS this would be natural, but, I have no idea how this is done in MVC.
Is there a templating library that can be used for this?
1) Create a model to Hold the Links
public class LinkObject
{
public string Link { get; set; }
public string Description { get; set; }
}
2) In your Action you can use ViewBag, ViewData or even pass the list inside you Model. I will show you how to do using ViewBag
public ActionResult MyDynamicView()
{
//Other stuff and code here
ViewBag.LinkList = new List<LinkObject>()
{
new LinkObject{ Link ="http://mylink1.com", Description = "Link 1"},
new LinkObject{ Link ="http://mylink2.com", Description = "Link 2"},
new LinkObject{ Link ="http://mylink3.com", Description = "Link 3"}
};
return View(/*pass the model if you have one*/);
}
3) In the View, just use a loop:
#foreach (var item in (List<LinkObject>)ViewBag.LinkList)
{
#item.Description
}
Just create a manual one for that, no need to do it from a template. For example, in javascript
function groupAnchor(url,display){
var a = document.createElement("a");
a.href = url;
a.className = "list-group-item";
a.target = "_blank";
a.innerHTML = display;
return a;
}
And then use that function to modify your html structure
<div id="anchors"></div>
<script>
document.getElementById("anchors").appendChild(groupAnchor("http://google.com","Google"));
</script>
Your approach to modification will more than likely be more advanced than this, but it demonstrates the concept. If you need these values to come from server side then you could always iterate over a set using #foreach() and issue either the whole html or script calls there -- or, pass the set from the server in as json and then use that in a function which is set up to manage a list of anchors.
To expand on this, it is important to avoid sending html to the view from a razor iteration. The reason being that html constructed by razor will increase the size of the page load, and if this is done in a list it can be a significant increase.
In your action, construct the list of links and then serialize them so they can be passed to the view
public ActionResult ViewWithLinks()
{
var vm = new ViewModel();
vm.Links = Json(LinkSource.ToList()).Data;
//or for a very simple test for proof of concept
var Numbers = Json(Enumerable.Range(0,100).ToList()).Data;
ViewData["numbers"] = Numbers ;
return View(vm);
}
where all you need is an object to hold the links in your view model
public class ViewModel
{
public ICollection<Link> Links { get; set; }
}
public class Link
{
public string text { get; set; }
public string href { get; set; }
}
and then in your view you can consume this json object
var allLinks = #Html.Raw(Json.Encode(Model.Links));
var numbersList = #Html.Raw(Json.Encode(ViewData["linkTest"]));//simple example
Now you can return to the above function in order to place it on the page by working with the array of link objects.
var $holder = $("<div>");
for(var i = 0; i < allLinks.length; i++){
$holder.append(groupAnchor(allLinks[i].href,allLinks[i].text));
}
$("#linkArea").append($holder);
The benefit is that all of this javascript can be cached for your page. It is loaded once and is capable of handling large amounts of links without having to worry about sending excessive html to the client.
I am using ASP.NET MVC3 for a form that has both server and client validations. I'm showing error messages as balloons above the inputs. Due to the presentation of the errors, I need to only show one error at a time, otherwise the balloons tend to obscure other fields that may also be in error.
How can I customize the validation behavior to only render the first error message?
Edit: Please notice that the form has both server and client validations, and that I only want to show the first error message for the entire form (not per field).
In case anyone needs it, the solution I came up with is to add the following script towards the bottom of the page. This hooks into the existing javascript validation to dynamically hide all but the first error in the form.
<script>
$(function() {
var form = $('form')[0];
var settings = $.data(form, 'validator').settings;
var errorPlacementFunction = settings.errorPlacement;
var successFunction = settings.success;
settings.errorPlacement = function(error, inputElement) {
errorPlacementFunction(error, inputElement);
showOneError();
}
settings.success = function (error) {
successFunction(error);
showOneError();
}
function showOneError() {
var $errors = $(form).find(".field-validation-error");
$errors.slice(1).hide();
$errors.filter(":first:not(:visible)").show();
}
});
</script>
Could give this a shot on your controller action
var goodErrors = ModelState.GroupBy(MS => MS.Key).Select(ms => ms.First()).ToDictionary(ms => ms.Key, ms => ms.Value);
ModelState.Clear();
foreach (var item in goodErrors)
{
ModelState.Add(item.Key, item.Value);
}
I'm just selecting only one of each property error, clearing all errors then adding the individual ones back.
this is completely untested but should work.
You could create a custom validation summary which would display only the first error. This could be done either by creating an extension for the HtmlHelper class, or by writing your own HtmlHelper. The former is the more straightforward.
public static class HtmlHelperExtensions
{
static string SingleMessageValidationSummary(this HtmlHelper helper, string validationMessage="")
{
string retVal = "";
if (helper.ViewData.ModelState.IsValid)
return "";
retVal += #"<div class=""notification-warnings""><span>";
if (!String.IsNullOrEmpty(validationMessage))
retVal += validationMessage;
retVal += "</span>";
retVal += #"<div class=""text"">";
foreach (var key in helper.ViewData.ModelState.Keys)
{
foreach(var err in helper.ViewData.ModelState[key].Errors)
retVal += "<p>" + err.ErrorMessage + "</p>";
break;
}
retVal += "</div></div>";
return retVal.ToString();
}
}
This is for the ValidationSummary, but the same can be done for ValidationMessageFor.
See: Custom ValidationSummary template Asp.net MVC 3
Edit: Client Side...
Update jquery.validate.unobstrusive.js. In particular the onError function, where it says error.removeClass("input-validation-error").appendTo(container);
Untested, but change that line to: error.removeClass("input-validation-error").eq(0).appendTo(container);
Create a html helper extension that renders only one message.
public static MvcHtmlString ValidationError(this HtmlHelper helper)
{
var result = new StringBuilder();
var tag = new TagBuilder("div");
tag.AddCssClass("validation-summary-errors");
var firstError = helper.ViewData.ModelState.SelectMany(k => k.Value.Errors).FirstOrDefault();
if (firstError != null)
{
tag.InnerHtml = firstError.ErrorMessage;
}
result.Append(tag.ToString());
return MvcHtmlString.Create(result.ToString());
}
Update the jquery.validate.unobtrusive.js OnErrors function as below,
function onErrors(form, validator) { // 'this' is the form element
// newly added condition
if ($(form.currentTarget).hasClass("one-error")) {
var container = $(this).find(".validation-summary-errors");
var firstError = validator.errorList[0];
$(container).html(firstError.message);
}
else {
var container = $(this).find("[data-valmsg-summary=true]"),
list = container.find("ul");
if (list && list.length && validator.errorList.length) {
list.empty();
container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
$.each(validator.errorList, function () {
$("<li />").html(this.message).appendTo(list);
});
}
}
}
Basically we have added a condition in the OnError to check whether the form contains a css-class named one-error and if yes then displays a single error else display all.
Is there a way to customize the validationSummary so that it can output anchor tags who's HREF is the name of the field that the validation message in teh summary is displaying for? This way, using jquery, i can add onclick events that focus the field when the anchor tag is clicked on the validation summary.
This is primarely for visually impaired people, so that when they have errors, the validation summary focuses, they tab to an error entry, the anchor tag with the field label focuses and the screen reader reads the anchor then the message, then they can click on the anchor to focus on the erroneous field.
First Name - Please enter your first name.
Thanks.
I don't think that there is any functionality within the framework for this so you would need to use a custom extension method. For example:
public static string AccessibleValidationSummary(this HtmlHelper htmlHelper, string message, IDictionary<string, object> htmlAttributes)
{
// Nothing to do if there aren't any errors
if (htmlHelper.ViewData.ModelState.IsValid)
{
return null;
}
string messageSpan;
if (!String.IsNullOrEmpty(message))
{
TagBuilder spanTag = new TagBuilder("span");
spanTag.MergeAttributes(htmlAttributes);
spanTag.MergeAttribute("class", HtmlHelper.ValidationSummaryCssClassName);
spanTag.SetInnerText(message);
messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
}
else
{
messageSpan = null;
}
StringBuilder htmlSummary = new StringBuilder();
TagBuilder unorderedList = new TagBuilder("ul");
unorderedList.MergeAttributes(htmlAttributes);
unorderedList.MergeAttribute("class", HtmlHelper.ValidationSummaryCssClassName);
foreach (string key in htmlHelper.ViewData.ModelState.Keys)
{
ModelState modelState = htmlHelper.ViewData.ModelState[key];
foreach (ModelError modelError in modelState.Errors)
{
string errorText = htmlHelper.ValidationMessage(key);
if (!String.IsNullOrEmpty(errorText))
{
TagBuilder listItem = new TagBuilder("li");
TagBuilder aTag = new TagBuilder("a");
aTag.Attributes.Add("href", "#" + key);
aTag.InnerHtml = errorText;
listItem.InnerHtml = aTag.ToString(TagRenderMode.Normal);
htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
}
}
}
unorderedList.InnerHtml = htmlSummary.ToString();
return messageSpan + unorderedList.ToString(TagRenderMode.Normal);
}
This is using the existing extension method from within the framework and changing the tag that is inserted into the list. This is a quick sample and there are some things to consider before using this:
This does not encode the error message as I have used the existing html.ValidationMessage. If you need to encode the message then you would need to include so code to provide default messages and any localization requirements.
Due to the use of the existing ValidationMessage method there is a span tag within the anchor. If you want to tidy your HTML then this should be replaced.
This is the most complicated of the usual overloads. If you want to use some of the simpler ones such as html.ValidationSummary() then you would need to create the relevant signatures and call to the method provided.
I'm using MvcRecaptcha to prevent bot posts for a complex unauthenticated client form on an ASP.NET MVC 2.0 site.
I only want to require one correct CAPTCHA entry from an unauthenticated client, even if some of the form's inputs are incorrect.
I have tried using a Session["CaptchaSuccess"] = true; variable to suppress Html.GenerateCaptcha() in my view following a successful entry, but the presence of the [CaptchaValidator] attribute on my [HttpPost] view causes an error because it naturally requires some ReCaptcha form inputs.
What is the simplest way to achieve this reliably, including on mobile browsers?
Solved by modifying the [CaptchaValidatorAttribute] OnActionExecuting method, where CaptchaSuccessFieldKey refers to the constant string value "CaptchaSuccess":
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
bool? bCaptchaSuccess = filterContext.HttpContext.Session[CaptchaSuccessFieldKey] as bool?;
if (bCaptchaSuccess.HasValue && bCaptchaSuccess.Value)
{
filterContext.ActionParameters["captchaValid"] = true;
}
else
{
var captchaChallengeValue = filterContext.HttpContext.Request.Form[ChallengeFieldKey];
var captchaResponseValue = filterContext.HttpContext.Request.Form[ResponseFieldKey];
var captchaValidtor = new Recaptcha.RecaptchaValidator
{
PrivateKey = ConfigurationManager.AppSettings["ReCaptchaPrivateKey"],
RemoteIP = filterContext.HttpContext.Request.UserHostAddress,
Challenge = captchaChallengeValue,
Response = captchaResponseValue
};
var recaptchaResponse = captchaValidtor.Validate();
// this will push the result value into a parameter in our Action
filterContext.ActionParameters["captchaValid"] = recaptchaResponse.IsValid;
}
base.OnActionExecuting(filterContext);
// Add string to Trace for testing
//filterContext.HttpContext.Trace.Write("Log: OnActionExecuting", String.Format("Calling {0}", filterContext.ActionDescriptor.ActionName));
}