← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 19:05:14 2015
Reported on Fri Jul 31 19:08:09 2015

Filename/var/www/foswiki11/lib/Foswiki/Validation.pm
StatementsExecuted 13 statements in 1.11ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
11118µs30µsFoswiki::Validation::::BEGIN@4Foswiki::Validation::BEGIN@4
1118µs14µsFoswiki::Validation::::BEGIN@5Foswiki::Validation::BEGIN@5
1117µs20µsFoswiki::Validation::::BEGIN@7Foswiki::Validation::BEGIN@7
1117µs37µsFoswiki::Validation::::BEGIN@54Foswiki::Validation::BEGIN@54
1114µs4µsFoswiki::Validation::::BEGIN@9Foswiki::Validation::BEGIN@9
1113µs3µsFoswiki::Validation::::BEGIN@10Foswiki::Validation::BEGIN@10
0000s0sFoswiki::Validation::::_getSecretFoswiki::Validation::_getSecret
0000s0sFoswiki::Validation::::_getSecretCookieNameFoswiki::Validation::_getSecretCookieName
0000s0sFoswiki::Validation::::addOnSubmitFoswiki::Validation::addOnSubmit
0000s0sFoswiki::Validation::::addValidationKeyFoswiki::Validation::addValidationKey
0000s0sFoswiki::Validation::::expireValidationKeysFoswiki::Validation::expireValidationKeys
0000s0sFoswiki::Validation::::getCookieFoswiki::Validation::getCookie
0000s0sFoswiki::Validation::::isValidNonceFoswiki::Validation::isValidNonce
0000s0sFoswiki::Validation::::validateFoswiki::Validation::validate
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# See bottom of file for license and copyright information
2package Foswiki::Validation;
3
4228µs243µs
# spent 30µs (18+12) within Foswiki::Validation::BEGIN@4 which was called: # once (18µs+12µs) by Foswiki::UI::BEGIN@160 at line 4
use strict;
# spent 30µs making 1 call to Foswiki::Validation::BEGIN@4 # spent 12µs making 1 call to strict::import
5223µs219µs
# spent 14µs (8+5) within Foswiki::Validation::BEGIN@5 which was called: # once (8µs+5µs) by Foswiki::UI::BEGIN@160 at line 5
use warnings;
# spent 14µs making 1 call to Foswiki::Validation::BEGIN@5 # spent 5µs making 1 call to warnings::import
6
7222µs232µs
# spent 20µs (7+13) within Foswiki::Validation::BEGIN@7 which was called: # once (7µs+13µs) by Foswiki::UI::BEGIN@160 at line 7
use Assert;
# spent 20µs making 1 call to Foswiki::Validation::BEGIN@7 # spent 13µs making 1 call to Assert::import
8
9217µs14µs
# spent 4µs within Foswiki::Validation::BEGIN@9 which was called: # once (4µs+0s) by Foswiki::UI::BEGIN@160 at line 9
use Digest::MD5 ();
# spent 4µs making 1 call to Foswiki::Validation::BEGIN@9
10240µs13µs
# spent 3µs within Foswiki::Validation::BEGIN@10 which was called: # once (3µs+0s) by Foswiki::UI::BEGIN@160 at line 10
use Foswiki ();
# spent 3µs making 1 call to Foswiki::Validation::BEGIN@10
11
12=begin TML
13
14---+ package Foswiki::Validation
15
16"Validation" is the process of ensuring that an incoming request came from
17a page we generated. Validation keys are injected into all HTML pages
18generated by Foswiki, in Foswiki::writeCompletePage. When a request is
19received from the browser that requires validation, that request must
20be accompanied by the validation key. The functions in this package
21support the generation and checking of these validation keys.
22
23Two key validation methods are supported by this module; simple token
24validation, and double-submission validation. Simple token validation
25stores a magic number in the session, and then adds that magic number to
26all forms in the output HTML. When a form is submitted, the magic number
27submitted with the form must match the number stored in the session. This is
28a relatively weak protection method, but requires some coding around so may
29discourage many hackers.
30
31The second method supported is properly called double cookie submission,
32but referred to as "strikeone" in Foswiki. This again uses a token added
33to output forms, but this time it uses Javascript to combine that token
34with a secret stored in a cookie, to create a new token. This is more secure
35because the cookie containing the secret cannot be read outside the domain
36of the server, making it much harder for a page hosted on an evil site to
37forge a valid transaction.
38
39When a request requiring validation comes in, Foswiki::UI::checkValidationKey
40is called. This compares the key in the request with the set of valid keys
41stored in the session. If the comparison fails, the browser is redirected
42to the =login= script (even if the user is currently logged in) with the
43=action= parameter set to =validate=. This generates a confirmation screen
44that the user must accept before the transaction can proceed. When the screen
45is confirmed, =login= is invoked again and the original transaction restored
46from passthrough.
47
48In the function descriptions below, $cgis is a reference to a CGI::Session
49object.
50
51=cut
52
53# Set to 1 to trace validation steps in STDERR
542982µs268µs
# spent 37µs (7+30) within Foswiki::Validation::BEGIN@54 which was called: # once (7µs+30µs) by Foswiki::UI::BEGIN@160 at line 54
use constant TRACE => 0;
# spent 37µs making 1 call to Foswiki::Validation::BEGIN@54 # spent 30µs making 1 call to constant::import
55
56# Define cookie name only once
57# WARNING: If you change this, be sure to also change the javascript
58sub _getSecretCookieName { 'FOSWIKISTRIKEONE' }
59
60=begin TML
61
62---++ StaticMethod addValidationKey( $cgis, $context, $strikeone ) -> $form
63
64Add a new validation key to a form. The key will time out after
65{Validation}{ValidForTime}.
66 * =$cgis= - a CGI::Session
67 * =$context= - the context for the key, usually the URL of the target
68 page plus the time. This should be unique for each rendered page.
69 * =$strikeone= - if set, expect the nonce to be combined with the
70 session secret before it is posted back.
71The validation key will be added as a hidden parameter at the end of
72the form tag.
73
74=cut
75
76sub addValidationKey {
77 my ( $cgis, $context, $strikeone ) = @_;
78 my $actions = $cgis->param('VALID_ACTIONS') || {};
79 my $nonce = Digest::MD5::md5_hex( $context, $cgis->id() );
80 my $action = $nonce;
81 if ($strikeone) {
82
83 # When using strikeone, the validation key pushed into the form will
84 # be combined with the secret in the cookie, and the combination
85 # will be md5 encoded before sending back. Since we know the secret
86 # and the validation key, then might as well save the hashed version.
87 # This has to be consistent with the algorithm in strikeone.js
88 my $secret = _getSecret($cgis);
89 $action = Digest::MD5::md5_hex( $nonce, $secret );
90
91 #print STDERR "V: STRIKEONE $nonce + $secret = $action\n" if TRACE;
92 }
93 my $timeout = time() + $Foswiki::cfg{Validation}{ValidForTime};
94 print STDERR "V: ADD KEY $action"
95 . ( $nonce ne $action ? "($nonce)" : '' ) . ' = '
96 . $timeout . "\n"
97 if TRACE && !defined $actions->{$action};
98 $actions->{$action} = $timeout;
99
100 $cgis->param( 'VALID_ACTIONS', $actions );
101
102 # Don't use CGI::hidden; it will inherit the URL param value of
103 # validation key and override our value :-(
104 return "<input type='hidden' name='validation_key' value='?$nonce' />";
105}
106
107=begin TML
108
109---++ StaticMethod addOnSubmit( $form ) -> $form
110
111Add a double submission onsubmit handler to a form.
112 * =$form= - the opening tag of a form, ie. &lt;form ...&gt;=
113The handler will be added to an existing on submit, or by adding a new
114onsubmit in the form tag.
115
116=cut
117
118sub addOnSubmit {
119 my ($form) = @_;
120 unless ( $form =~
121s/\bonsubmit=(["'])((?:\s*javascript:)?)(.*)\1/onsubmit=${1}${2}StrikeOne.submit(this);$3$1/i
122 )
123 {
124 $form =~ s/>$/ onsubmit="StrikeOne.submit(this)">/;
125 }
126 return $form;
127}
128
129=begin TML
130
131---++ StaticMethod getCookie( $cgis ) -> $cookie
132
133Get a double submission cookie
134 * =$cgis= - a CGI::Session
135
136The cookie is a non-HttpOnly cookie that contains the current session ID
137and a secret. The secret is constant for a given session.
138
139The caller should adjust the =-secure= flag of the cookie, according to the
140request being processed.
141
142=cut
143
144sub getCookie {
145 my ($cgis) = @_;
146
147 my $secret = _getSecret($cgis);
148
149 # Add the cookie to the response
150 require CGI::Cookie;
151 my $cookie = CGI::Cookie->new(
152 -name => _getSecretCookieName(),
153 -value => $secret,
154 -path => '/',
155 -httponly => 0, # we *want* JS to be able to read it!
156 );
157
158 return $cookie;
159}
160
161=begin TML
162
163---++ StaticMethod isValidNonce( $cgis, $key ) -> $boolean
164
165Check that the given validation key is valid for the session.
166Return false if not.
167
168=cut
169
170sub isValidNonce {
171 my ( $cgis, $nonce ) = @_;
172 return 1 if ( $Foswiki::cfg{Validation}{Method} eq 'none' );
173 return 0 unless defined $nonce;
174 $nonce =~ s/^\?// if ( $Foswiki::cfg{Validation}{Method} ne 'strikeone' );
175 my $actions = $cgis->param('VALID_ACTIONS');
176 return 0 unless ref($actions) eq 'HASH';
177 print STDERR "V: CHECK $nonce -> " . ( $actions->{$nonce} ? 1 : 0 ) . "\n"
178 if TRACE;
179 return $actions->{$nonce};
180}
181
182=begin TML
183
184---++ StaticMethod expireValidationKeys($cgis[, $key])
185
186Expire any timed-out validation keys for this session, and (optionally)
187force expiry of a specific key, even if it hasn't timed out.
188
189=cut
190
191sub expireValidationKeys {
192 my ( $cgis, $key ) = @_;
193 my $actions = $cgis->param('VALID_ACTIONS');
194 if ($actions) {
195 if ( defined $key && exists $actions->{$key} ) {
196 $actions->{$key} = 0; # force-expire this key
197 }
198 my $deaths = 0;
199 my $now = time();
200 while ( my ( $nonce, $time ) = each %$actions ) {
201 if ( $time < $now ) {
202
203 print STDERR "V: EXPIRE $nonce $time\n" if TRACE;
204 delete $actions->{$nonce};
205 $deaths++;
206 }
207 }
208
209 # If we have more than the permitted number of keys, expire
210 # the oldest ones.
211 my $excess =
212 scalar( keys %$actions ) -
213 $Foswiki::cfg{Validation}{MaxKeysPerSession};
214 if ( $excess > 0 ) {
215 print STDERR "V: $excess TOO MANY KEYS\n" if TRACE;
216 my @keys = sort { $actions->{$a} <=> $actions->{$b} }
217 keys %$actions;
218 while ( $excess-- > 0 ) {
219 my $key = shift(@keys);
220 print STDERR "V: EXPIRE $key $actions->{$key}\n" if TRACE;
221 delete $actions->{$key};
222 $deaths++;
223 }
224 }
225 if ($deaths) {
226 $cgis->param( 'VALID_ACTIONS', $actions );
227 }
228 }
229}
230
231=begin TML
232
233---++ StaticMethod validate($session)
234
235Generate (or check) the "Suspicious request" verification screen for the
236given session. This screen is generated when a validation fails, as a
237response to a ValidationException.
238
239=cut
240
241sub validate {
242 my ($session) = @_;
243 my $query = $session->{request};
244 my $web = $session->{webName};
245 my $topic = $session->{topicName};
246 my $cgis = $session->getCGISession();
247
248 my $tmpl = $session->templates->readTemplate('validate');
249
250 if ( $query->param('response') ) {
251 my $cacheUID = $query->param('foswikioriginalquery');
252 $query->delete('foswikioriginalquery');
253 my $url;
254 if ( $query->param('response') eq 'OK'
255 && isValidNonce( $cgis, $query->param('validation_key') ) )
256 {
257 if ( !$cacheUID ) {
258 $url = $session->getScriptUrl( 0, 'view', $web, $topic );
259 }
260 else {
261
262 # Reload the cached original query over the current query.
263 # When the redirect is validated it should pass, because
264 # it will now be using the validation code from the
265 # confirmation screen that brought us here.
266 require Foswiki::Request::Cache;
267 Foswiki::Request::Cache->new()->load( $cacheUID, $query );
268 $url = $query->url();
269 }
270
271 # Complete the query by passing the query on
272 # with passthrough
273 print STDERR "WV: CONFIRMED; POST to $url\n" if TRACE;
274 $session->redirect( $url, 1 );
275 }
276 else {
277 print STDERR "V: CONFIRMATION REJECTED\n" if TRACE;
278
279 # Validation failed; redirect to view (302)
280 $url = $session->getScriptUrl( 0, 'view', $web, $topic );
281 $session->redirect( $url, 0 ); # no passthrough
282 }
283 }
284 else {
285
286 print STDERR "V: PROMPTING FOR CONFIRMATION " . $query->uri() . "\n"
287 if TRACE;
288
289 # Prompt for user verification - code 419 chosen by foswiki devs.
290 # None of the defined HTTP codes describe what is really happening,
291 # which is why we chose a "new" code. The confirmation page
292 # isn't a conflict, not a security issue, and we cannot use 403
293 # because there is a high probability this would get caught by
294 # Apache to send back the Registation page. We didn't want any
295 # installation to catch the HTTP return code we were sending back,
296 # as we need this page to arrive intact to the user, otherwise
297 # they won't be able to do anything. 419 is a placebo, and if it
298 # is ever defined can be replaced by any other undefined 4xx code.
299 $session->{response}->status(419);
300
301 my $topicObject = Foswiki::Meta->new( $session, $web, $topic );
302 $tmpl = $topicObject->expandMacros($tmpl);
303 $tmpl = $topicObject->renderTML($tmpl);
304 $tmpl =~ s/<nop>//g;
305
306 $session->writeCompletePage($tmpl);
307 }
308}
309
310# Get/set the one-strike secret in the CGI::Session
311sub _getSecret {
312 my $cgis = shift;
313 my $secret = $cgis->param( _getSecretCookieName() );
314 unless ($secret) {
315
316 # Use hex encoding to make it cookie-friendly
317 $secret = Digest::MD5::md5_hex( $cgis->id(), rand(time) );
318 $cgis->param( _getSecretCookieName(), $secret );
319 }
320 return $secret;
321}
322
32312µs1;
324__END__