← Index
NYTProf Performance Profile   « block view • line view • sub view »
For /usr/local/src/github.com/foswiki/core/bin/view
  Run on Sun Dec 4 17:17:59 2011
Reported on Sun Dec 4 17:26:43 2011

Filename/usr/local/src/github.com/foswiki/core/lib/Foswiki/Validation.pm
StatementsExecuted 69 statements in 2.88ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
111108µs147µsFoswiki::Validation::::expireValidationKeysFoswiki::Validation::expireValidationKeys
11184µs290µsFoswiki::Validation::::generateValidationKeyFoswiki::Validation::generateValidationKey
11155µs557µsFoswiki::Validation::::getCookieFoswiki::Validation::getCookie
22149µs99µsFoswiki::Validation::::_getSecretFoswiki::Validation::_getSecret
11137µs60µsFoswiki::Validation::::addOnSubmitFoswiki::Validation::addOnSubmit
11129µs319µsFoswiki::Validation::::addValidationKeyFoswiki::Validation::addValidationKey
11124µs31µsFoswiki::Validation::::BEGIN@4Foswiki::Validation::BEGIN@4
22122µs22µsFoswiki::Validation::::CORE:substFoswiki::Validation::CORE:subst (opcode)
11121µs57µsFoswiki::Validation::::BEGIN@7Foswiki::Validation::BEGIN@7
11116µs34µsFoswiki::Validation::::BEGIN@5Foswiki::Validation::BEGIN@5
11116µs129µsFoswiki::Validation::::BEGIN@54Foswiki::Validation::BEGIN@54
32113µs13µsFoswiki::Validation::::_getSecretCookieNameFoswiki::Validation::_getSecretCookieName
1119µs9µsFoswiki::Validation::::BEGIN@9Foswiki::Validation::BEGIN@9
1118µs8µsFoswiki::Validation::::BEGIN@10Foswiki::Validation::BEGIN@10
0000s0sFoswiki::Validation::::isValidNonceFoswiki::Validation::isValidNonce
0000s0sFoswiki::Validation::::isValidNonceHashFoswiki::Validation::isValidNonceHash
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
4245µs238µs
# spent 31µs (24+7) within Foswiki::Validation::BEGIN@4 which was called: # once (24µs+7µs) by Foswiki::UI::BEGIN@160 at line 4
use strict;
# spent 31µs making 1 call to Foswiki::Validation::BEGIN@4 # spent 7µs making 1 call to strict::import
5244µs252µs
# spent 34µs (16+18) within Foswiki::Validation::BEGIN@5 which was called: # once (16µs+18µs) by Foswiki::UI::BEGIN@160 at line 5
use warnings;
# spent 34µs making 1 call to Foswiki::Validation::BEGIN@5 # spent 18µs making 1 call to warnings::import
6
7244µs294µs
# spent 57µs (21+37) within Foswiki::Validation::BEGIN@7 which was called: # once (21µs+37µs) by Foswiki::UI::BEGIN@160 at line 7
use Assert;
# spent 57µs making 1 call to Foswiki::Validation::BEGIN@7 # spent 37µs making 1 call to Assert::import
8
9237µs19µs
# spent 9µs within Foswiki::Validation::BEGIN@9 which was called: # once (9µs+0s) by Foswiki::UI::BEGIN@160 at line 9
use Digest::MD5 ();
# spent 9µs making 1 call to Foswiki::Validation::BEGIN@9
10289µs18µs
# spent 8µs within Foswiki::Validation::BEGIN@10 which was called: # once (8µs+0s) by Foswiki::UI::BEGIN@160 at line 10
use Foswiki ();
# spent 8µ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
5422.19ms2243µs
# spent 129µs (16+113) within Foswiki::Validation::BEGIN@54 which was called: # once (16µs+113µs) by Foswiki::UI::BEGIN@160 at line 54
use constant TRACE => 0;
# spent 129µs making 1 call to Foswiki::Validation::BEGIN@54 # spent 114µ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
58327µs
# spent 13µs within Foswiki::Validation::_getSecretCookieName which was called 3 times, avg 4µs/call: # 2 times (10µs+0s) by Foswiki::Validation::_getSecret at line 350, avg 5µs/call # once (4µs+0s) by Foswiki::Validation::getCookie at line 174
sub _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
76
# spent 319µs (29+290) within Foswiki::Validation::addValidationKey which was called: # once (29µs+290µs) by Foswiki::writeCompletePage at line 781 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm
sub addValidationKey {
77326µs my ( $cgis, $context, $strikeone ) = @_;
78
791290µs my $nonce = generateValidationKey( $cgis, $context, $strikeone );
# spent 290µs making 1 call to Foswiki::Validation::generateValidationKey
80
81 # Don't use CGI::hidden; it will inherit the URL param value of
82 # validation key and override our value :-(
83 return "<input type='hidden' name='validation_key' value='?$nonce' />";
84}
85
86=begin TML
87
88---++ StaticMethod generateValidationKey( $cgis, $context, $strikeone ) -> $nonce
89
90Generate a new validation key. The key will time out after
91{Validation}{ValidForTime}.
92 * =$cgis= - a CGI::Session
93 * =$context= - the context for the key, usually the URL of the target
94 page plus the time. This should be unique for each rendered page.
95 * =$strikeone= - if set, expect the nonce to be combined with the
96 session secret before it is posted back.
97The validation key wcan then be used in a HTML form, or headers for RestPlugin API etc.
98TODO: should this be assable from Foswiki::Func so that RestHandlers can use it too?
99
100=cut
101
102
# spent 290µs (84+205) within Foswiki::Validation::generateValidationKey which was called: # once (84µs+205µs) by Foswiki::Validation::addValidationKey at line 79
sub generateValidationKey {
103990µs my ( $cgis, $context, $strikeone ) = @_;
104121µs my $actions = $cgis->param('VALID_ACTIONS') || {};
# spent 21µs making 1 call to CGI::Session::param
105257µs my $nonce = Digest::MD5::md5_hex( $context, $cgis->id() );
# spent 36µs making 1 call to CGI::Session::id # spent 22µs making 1 call to Digest::MD5::md5_hex
106 my $action = $nonce;
107220µs if ($strikeone) {
108
109 # When using strikeone, the validation key pushed into the form will
110 # be combined with the secret in the cookie, and the combination
111 # will be md5 encoded before sending back. Since we know the secret
112 # and the validation key, then might as well save the hashed version.
113 # This has to be consistent with the algorithm in strikeone.js
114142µs my $secret = _getSecret($cgis);
# spent 42µs making 1 call to Foswiki::Validation::_getSecret
11515µs $action = Digest::MD5::md5_hex( $nonce, $secret );
# spent 5µs making 1 call to Digest::MD5::md5_hex
116
117 #print STDERR "V: STRIKEONE $nonce + $secret = $action\n" if TRACE;
118 }
119 my $timeout = time() + $Foswiki::cfg{Validation}{ValidForTime};
120 print STDERR "V: ADD KEY $action"
121 . ( $nonce ne $action ? "($nonce)" : '' ) . ' = '
122 . $timeout . "\n"
123 if TRACE && !defined $actions->{$action};
124 $actions->{$action} = $timeout;
125
126 #used to store the actions in case there are more than one form..
127180µs $cgis->param( 'VALID_ACTIONS', $actions );
# spent 80µs making 1 call to CGI::Session::param
128
129 return $nonce;
130}
131
132=begin TML
133
134---++ StaticMethod addOnSubmit( $form ) -> $form
135
136Add a double submission onsubmit handler to a form.
137 * =$form= - the opening tag of a form, ie. &lt;form ...&gt;=
138The handler will be added to an existing on submit, or by adding a new
139onsubmit in the form tag.
140
141=cut
142
143
# spent 60µs (37+22) within Foswiki::Validation::addOnSubmit which was called: # once (37µs+22µs) by Foswiki::writeCompletePage at line 755 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm
sub addOnSubmit {
144342µs my ($form) = @_;
145117µs113µs unless ( $form =~
# spent 13µs making 1 call to Foswiki::Validation::CORE:subst
146s/\bonsubmit=(["'])((?:\s*javascript:)?)(.*)\1/onsubmit=${1}${2}StrikeOne.submit(this);$3$1/i
147 )
148 {
14919µs $form =~ s/>$/ onsubmit="StrikeOne.submit(this)">/;
# spent 9µs making 1 call to Foswiki::Validation::CORE:subst
150 }
151 return $form;
152}
153
154=begin TML
155
156---++ StaticMethod getCookie( $cgis ) -> $cookie
157
158Get a double submission cookie
159 * =$cgis= - a CGI::Session
160
161The cookie is a non-HttpOnly cookie that contains the current session ID
162and a secret. The secret is constant for a given session.
163
164=cut
165
166
# spent 557µs (55+502) within Foswiki::Validation::getCookie which was called: # once (55µs+502µs) by Foswiki::writeCompletePage at line 761 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm
sub getCookie {
167553µs my ($cgis) = @_;
168
169157µs my $secret = _getSecret($cgis);
# spent 57µs making 1 call to Foswiki::Validation::_getSecret
170
171 # Add the cookie to the response
172 # TODO: -secure option should be abstraced out - see comments on Item:10061
173 require CGI::Cookie;
1742445µs my $cookie = CGI::Cookie->new(
# spent 441µs making 1 call to CGI::Cookie::new # spent 4µs making 1 call to Foswiki::Validation::_getSecretCookieName
175 -name => _getSecretCookieName(),
176 -value => $secret,
177 -path => '/',
178 -httponly => 0, # we *want* JS to be able to read it!
179 );
180
181 return $cookie;
182}
183
184=begin TML
185
186---++ StaticMethod isValidNonce( $cgis, $key ) -> $boolean
187
188Check that the given validation key is valid for the session.
189Return false if not.
190
191=cut
192
193sub isValidNonce {
194 my ( $cgis, $nonce ) = @_;
195 my $actions = $cgis->param('VALID_ACTIONS');
196 return isValidNonceHash( $actions, $nonce );
197}
198
199=begin TML
200
201---++ StaticMethod isValidNonceHash( $actions, $key ) -> $boolean
202
203Check that the given validation key is valid for the session.
204Return false if not.
205
206=cut
207
208sub isValidNonceHash {
209 my ( $actions, $nonce ) = @_;
210 return 1 if ( $Foswiki::cfg{Validation}{Method} eq 'none' );
211 return 0 unless defined $nonce;
212 $nonce =~ s/^\?// if ( $Foswiki::cfg{Validation}{Method} ne 'strikeone' );
213 return 0 unless ref($actions) eq 'HASH';
214 print STDERR "V: CHECK $nonce -> " . ( $actions->{$nonce} ? 1 : 0 ) . "\n"
215 if TRACE;
216 return $actions->{$nonce};
217}
218
219=begin TML
220
221---++ StaticMethod expireValidationKeys($cgis[, $key])
222
223Expire any timed-out validation keys for this session, and (optionally)
224force expiry of a specific key, even if it hasn't timed out.
225
226=cut
227
228
# spent 147µs (108+39) within Foswiki::Validation::expireValidationKeys which was called: # once (108µs+39µs) by Foswiki::writeCompletePage at line 745 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm
sub expireValidationKeys {
229327µs my ( $cgis, $key ) = @_;
230139µs my $actions = $cgis->param('VALID_ACTIONS');
# spent 39µs making 1 call to CGI::Session::param
231765µs if ($actions) {
232 if ( defined $key && exists $actions->{$key} ) {
233 $actions->{$key} = 0; # force-expire this key
234 }
235 my $deaths = 0;
236 my $now = time();
2371215µs while ( my ( $nonce, $time ) = each %$actions ) {
238 if ( $time < $now ) {
239
240 print STDERR "V: EXPIRE $nonce $time\n" if TRACE;
241 delete $actions->{$nonce};
242 $deaths++;
243 }
244 }
245
246 # If we have more than the permitted number of keys, expire
247 # the oldest ones.
248 my $excess =
249 scalar( keys %$actions ) -
250 $Foswiki::cfg{Validation}{MaxKeysPerSession};
251 if ( $excess > 0 ) {
252 print STDERR "V: $excess TOO MANY KEYS\n" if TRACE;
253 my @keys = sort { $actions->{$a} <=> $actions->{$b} }
254 keys %$actions;
255 while ( $excess-- > 0 ) {
256 my $key = shift(@keys);
257 print STDERR "V: EXPIRE $key $actions->{$key}\n" if TRACE;
258 delete $actions->{$key};
259 $deaths++;
260 }
261 }
262 if ($deaths) {
263 $cgis->param( 'VALID_ACTIONS', $actions );
264 }
265 }
266}
267
268=begin TML
269
270---++ StaticMethod validate($session)
271
272Generate (or check) the "Suspicious request" verification screen for the
273given session. This screen is generated when a validation fails, as a
274response to a ValidationException.
275
276=cut
277
278sub validate {
279 my ($session) = @_;
280 my $query = $session->{request};
281 my $web = $session->{webName};
282 my $topic = $session->{topicName};
283 my $cgis = $session->getCGISession();
284
285 my $tmpl = $session->templates->readTemplate('validate');
286
287 if ( $query->param('response') ) {
288 my $cacheUID = $query->param('foswikioriginalquery');
289 $query->delete('foswikioriginalquery');
290 my $url;
291 if ( $query->param('response') eq 'OK'
292 && isValidNonce( $cgis, $query->param('validation_key') ) )
293 {
294 if ( !$cacheUID ) {
295 $url = $session->getScriptUrl( 0, 'view', $web, $topic );
296 }
297 else {
298
299 # Reload the cached original query over the current query.
300 # When the redirect is validated it should pass, because
301 # it will now be using the validation code from the
302 # confirmation screen that brought us here.
303 require Foswiki::Request::Cache;
304 Foswiki::Request::Cache->new()->load( $cacheUID, $query );
305 $url = $query->url();
306 }
307
308 # Complete the query by passing the query on
309 # with passthrough
310 print STDERR "WV: CONFIRMED; POST to $url\n" if TRACE;
311 $session->redirect( $url, 1 );
312 }
313 else {
314 print STDERR "V: CONFIRMATION REJECTED\n" if TRACE;
315
316 # Validation failed; redirect to view (302)
317 $url = $session->getScriptUrl( 0, 'view', $web, $topic );
318 $session->redirect( $url, 0 ); # no passthrough
319 }
320 }
321 else {
322
323 print STDERR "V: PROMPTING FOR CONFIRMATION " . $query->uri() . "\n"
324 if TRACE;
325
326 # Prompt for user verification - code 419 chosen by foswiki devs.
327 # None of the defined HTTP codes describe what is really happening,
328 # which is why we chose a "new" code. The confirmation page
329 # isn't a conflict, not a security issue, and we cannot use 403
330 # because there is a high probability this would get caught by
331 # Apache to send back the Registation page. We didn't want any
332 # installation to catch the HTTP return code we were sending back,
333 # as we need this page to arrive intact to the user, otherwise
334 # they won't be able to do anything. 419 is a placebo, and if it
335 # is ever defined can be replaced by any other undefined 4xx code.
336 $session->{response}->status(419);
337
338 my $topicObject = Foswiki::Meta->new( $session, $web, $topic );
339 $tmpl = $topicObject->expandMacros($tmpl);
340 $tmpl = $topicObject->renderTML($tmpl);
341 $tmpl =~ s/<nop>//g;
342
343 $session->writeCompletePage($tmpl);
344 }
345}
346
347# Get/set the one-strike secret in the CGI::Session
348
# spent 99µs (49+50) within Foswiki::Validation::_getSecret which was called 2 times, avg 50µs/call: # once (30µs+27µs) by Foswiki::Validation::getCookie at line 169 # once (19µs+23µs) by Foswiki::Validation::generateValidationKey at line 114
sub _getSecret {
349846µs my $cgis = shift;
350450µs my $secret = $cgis->param( _getSecretCookieName() );
# spent 41µs making 2 calls to CGI::Session::param, avg 20µs/call # spent 10µs making 2 calls to Foswiki::Validation::_getSecretCookieName, avg 5µs/call
351 unless ($secret) {
352
353 # Use hex encoding to make it cookie-friendly
354 $secret = Digest::MD5::md5_hex( $cgis->id(), rand(time) );
355 $cgis->param( _getSecretCookieName(), $secret );
356 }
357 return $secret;
358}
359
36015µs1;
361__END__
 
# spent 22µs within Foswiki::Validation::CORE:subst which was called 2 times, avg 11µs/call: # once (13µs+0s) by Foswiki::Validation::addOnSubmit at line 145 # once (9µs+0s) by Foswiki::Validation::addOnSubmit at line 149
sub Foswiki::Validation::CORE:subst; # opcode