← 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:27:08 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 {
7714µs my ( $cgis, $context, $strikeone ) = @_;
78
7917µs1290µ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 :-(
83114µs 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 {
10313µs my ( $cgis, $context, $strikeone ) = @_;
10416µs121µs my $actions = $cgis->param('VALID_ACTIONS') || {};
# spent 21µs making 1 call to CGI::Session::param
105147µs257µ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
10612µs my $action = $nonce;
10713µ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
11416µs142µs my $secret = _getSecret($cgis);
# spent 42µs making 1 call to Foswiki::Validation::_getSecret
115114µs15µ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 }
11913µs 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};
12416µs $actions->{$action} = $timeout;
125
126 #used to store the actions in case there are more than one form..
12716µs180µs $cgis->param( 'VALID_ACTIONS', $actions );
# spent 80µs making 1 call to CGI::Session::param
128
129113µs 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 {
14414µs my ($form) = @_;
145128µ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 {
149117µs19µs $form =~ s/>$/ onsubmit="StrikeOne.submit(this)">/;
# spent 9µs making 1 call to Foswiki::Validation::CORE:subst
150 }
151110µs 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 {
16712µs my ($cgis) = @_;
168
16918µs157µ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
17313µs require CGI::Cookie;
174129µs2445µ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
181111µs 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 {
22912µs my ( $cgis, $key ) = @_;
230113µs139µs my $actions = $cgis->param('VALID_ACTIONS');
# spent 39µs making 1 call to CGI::Session::param
231112µs if ($actions) {
23211µs if ( defined $key && exists $actions->{$key} ) {
233 $actions->{$key} = 0; # force-expire this key
234 }
23512µs my $deaths = 0;
23613µs my $now = time();
237152µs while ( my ( $nonce, $time ) = each %$actions ) {
2381215µs 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.
24814µs my $excess =
249 scalar( keys %$actions ) -
250 $Foswiki::cfg{Validation}{MaxKeysPerSession};
25112µs 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 }
26212µs 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 {
34923µs my $cgis = shift;
350220µs450µ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
35123µs 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 }
357219µs 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