How to integrate with request validation (strikeone)
The process of request validation is described in outline in the
support article on security features. This article is aimed at developers who need to know a bit more of the nitty-gritty in order to use, or abuse, validation. it's assumed the reader has read the outline article - I'm not going to repeat anything that's already there.
Important code:
-
Foswiki::Validation
-
Foswiki::writeCompletePage
Overview of Validation
First, an overview of how validation works. The secret to integrating with validation is to understand each of these steps and make sure they are implemented in the right way for any validated requests you make.
Step 1: Getting the validation code into a Foswiki page
This is done by all the standard scripts as the last step in the rendering pipeline, in
writeCompletePage
. It:
- finds all forms that use method
POST
in the output HTML, and edits them to add a hidden input containing the validation key,
- calculates the expected value of this key when it is combined with the secret cookie, and stores it in the CGI session,
- adds an
onsubmit
handler to each form, to invoke the JS that combines the key with the secret cookie,
- adds the
strikeone.js
module into the HEAD of the page,
- adds the secret cookie to the response.
Of course, if you write your own REST handler and don't return through a path that includes a call to
writeCompletePage
, you won't get these steps performed, and will have to perform them yourself.
Note that forms that need a validation key
must use
method="post"
.
When the user submits the form, the
onsubmit
handler in
strikeone.js
is called. This combines the secret cookie with the
validation_key
parameter in the form to generate a new key, which it writes back into the
validation_key
input in the DOM. This step has to be done by Javascript, because it requires access to the cookie and the DOM to compose the key.
Step 3: Handling validation in the server
For standard system scripts such as
save
and
upload
, the
Foswiki::UI
package that implements the script will call
Foswiki::UI::checkValidationKey
. This function checks that the key is valid and if not, throws a
Foswiki::ValidationException
. This exception is handled in
Foswiki::UI
and results in a redirect to the confirmation screen. If the key is valid, and
{Validation}{ExpireKeyOnUse}
is true, it will be deleted from the session (expired).
Use Cases
Now that you have an overview of the validation process, here are some typical use cases.
I want my REST handler to generate an HTML page with validated forms on it
As long as HTML is run though
Foswiki::writeCompletePage
, then it will get the validation code attached to any embedded forms, as described in Step 1. If your REST handler returns with a 'true' result, then this result will be run through
Foswiki::writeCompletePage
, and validation will work as normal.
In Foswiki 1.2 and later, you can use the %NONCE% macro to embed a validation code in HTML - for example, in HTML5 attributes. Prior to 1.2 I'm afraid it's up to you.
Javascript wants to make lots of requests
Let's say your page contains a button that you want to be able to click repeatedly, with each click making a validated request back to the server. The key originally issued with the page will be expired after the first request, so you need a way to issue a new key back to the client, that it can use in the next request. We assume that the button is handled with Javascript, and you are able to perform some action on the result of the request.
Let's say we have a REST handler declared as follows:
Foswiki::Func::registerRESTHandler(
'wibble', \&_restWibble,
validate => 1,
http_allow => 'POST'
);
with a handler function as follows:
sub _restWibble {
my $session = shift;
my $response = $session->{response};
my $request = Foswiki::Func::getCgiQuery();
...
return undef; # *DO NOT* run the response through writeCompletePage
}
Whenever the Javascript posts a request to the server, the REST handler must generate a new key which can be passed to the client JS. Foswiki 1.2 automatically includes a new
X-Foswiki-Validation
HTTP header whenever it sees a validated POST request, so in this case the work is done for you. Prior to 1.2, you have to DIY in your REST handler, as follows:
# Add new validation key to HTTP header
my $cgis = $session->getCGISession();
my $context = $request->url( -full => 1, -path => 1, -query => 1 ) . time();
if ( $Foswiki::cfg{Validation}{Method} eq 'strikeone' ) {
require Foswiki::Validation;
my $nonce;
# Pre 1.2.0 compatibility
my $html = Foswiki::Validation::addValidationKey( $cgis, $context, 1 );
$nonce = $1 if ( $html =~ /value=['"]\?(.*?)['"]/ );
$response->pushHeader( 'X-Foswiki-Validation', $nonce ) if defined $nonce;
}
On the client, the Javascript has to update the key in the forms in the DOM. For example, let's say we have the following (JQuery) REST request:
$.ajax({
url: form.action,
type: "POST",
data: $(form).serialize(),
complete: function(jqXHR, textStatus) {
var nonce = jqXHR.getResponseHeader(
'X-Foswiki-Validation');
if (nonce) {
$("input[name='validation_key']").each(function() {
$(this).val("?" + nonce);
});
}
}
});
There are no forms on my page!
By default validation keys are generated for each form on an HTML page that has been generated by running through
writeCompletePage
. Javascript can reap keys from any forms it finds in the page, job done. But what if there are no forms, and therefore no keys? You could pass the key using an HTTP header, as described for REST above, but that would mean you only had one key for the entire page, which would restrict your ability to do overlapping AJAX calls. A better approach is to embed it in the HTML. In Foswiki 1.2 and later this couldn't be simpler - there is a %NONCE% macro that generates a key for you. For example, to pass a key using HTML5 data attributes:
<a data-validate="?%NONCE%">...</a>
Prior to 1.2 the only option is to DIY. See SubscribePlugin for an example of this.
Contributors: CrawfordCurrie,
OliverKrueger