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:
  1. finds all forms that use method POST in the output HTML, and edits them to add a hidden input containing the validation key,
  2. calculates the expected value of this key when it is combined with the secret cookie, and stores it in the CGI session,
  3. adds an onsubmit handler to each form, to invoke the JS that combines the key with the secret cookie,
  4. adds the strikeone.js module into the HEAD of the page,
  5. 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".

Step 2: Composing the validation key when the form is submitted

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

BasicForm edit

TopicClassification DeveloperDocumentation
TopicSummary Developer how-to integrate with request validation (strikeone)
InterestedParties
Topic revision: r11 - 20 Jun 2018, DanielOConnor
The copyright of the content on this website is held by the contributing authors, except where stated elsewhere. See Copyright Statement. Creative Commons License    Legal Imprint    Privacy Policy