Skip to content

Commit

Permalink
Item1568: second pass at validation code
Browse files Browse the repository at this point in the history
git-svn-id: http://svn.foswiki.org/branches/Release01x00@3918 0b4bb1d4-4e5a-0410-9cc4-b2b747904278
  • Loading branch information
CrawfordCurrie authored and CrawfordCurrie committed May 16, 2009
1 parent fd1dc54 commit 036ef03
Show file tree
Hide file tree
Showing 10 changed files with 307 additions and 78 deletions.
85 changes: 28 additions & 57 deletions core/lib/Foswiki.pm
Expand Up @@ -47,12 +47,14 @@ with CGI accelerators such as mod_perl.
use strict;
use Assert;
use Error qw( :try );
use CGI; # Always required to get html generation tags;
use Digest::MD5 ();

use Foswiki::Response;
use Foswiki::Request;
use Foswiki::Logger;
use Fcntl; # File control constants e.g. O_EXCL
use CGI (); # Always required to get html generation tags;
use Digest::MD5 (); # For passthru and validation

use Foswiki::Response ();
use Foswiki::Request ();
use Foswiki::Logger ();

require 5.005; # For regex objects and internationalisation

Expand Down Expand Up @@ -631,10 +633,16 @@ sub writeCompletePage {
$text =~ s/([\t ]?)[ \t]*<\/?(nop|noautolink)\/?>/$1/gis;
$text .= "\n" unless $text =~ /\n$/s;

if ( $contentType eq 'text/html' ) {
$this->_clearValidationKeys();
$text =~
s/(<form[^>]*method=['"]POST['"][^>]*>)/$this->_addValidationKey( $1 )/gei;
my $cgis = $this->getCGISession();
if ( $cgis && $contentType eq 'text/html' ) {
# Don't expire the validation key through login, or when
# endpoint is an error.
Foswiki::Validation::expireValidationKeys($cgis)
unless ($this->{request}->action() eq 'login'
or ( $ENV{REDIRECT_STATUS} || 0 ) >= 400);
# Inject validation key in HTML forms
$text =~ s/(<form[^>]*method=['"]POST['"][^>]*>)/
Foswiki::Validation::addValidationKey( $cgis, $1 )/gei;
}
my $htmlHeader = join( "\n",
map { '<!--' . $_ . '-->' . $this->{_HTMLHEADERS}{$_} }
Expand Down Expand Up @@ -933,66 +941,29 @@ sub cacheQuery {
my $uid = $this->{digester}->hexdigest();
my $passthruFilename = "$Foswiki::cfg{WorkingDir}/tmp/passthru_$uid";

use Fcntl;

#passthrough file is only written to once, so if it already exists, suspect a security hack (O_EXCL)
# passthrough file is only written to once, so if it already exists,
# suspect a security hack (O_EXCL)
sysopen( F, "$passthruFilename", O_RDWR | O_EXCL | O_CREAT, 0600 )
|| die
"Unable to open $Foswiki::cfg{WorkingDir}/tmp for write; check the setting of {WorkingDir} in configure, and check file permissions: $!";
|| die 'Unable to open '.$Foswiki::cfg{WorkingDir}
.'/tmp for write; check the setting of {WorkingDir} in configure,'
.' and check file permissions: '.$!;
$query->save( \*F );
close(F);
return 'foswiki_redirect_cache=' . $uid;
}

sub _getValidationKey {
my ( $this, $action ) = @_;
my $cgis = $this->{users}->{loginManager}->{_cgisession};
$this->{digester}->add( $action, $cgis->id(), rand(time) );
return $this->{digester}->b64digest();
}

# Clear the set of validation keys for this session
sub _clearValidationKeys {
my $this = shift;
my $cgis = $this->{users}->{loginManager}->{_cgisession};

# This should only be done when the page isn't a login page
# nor a 401 redirect (ApacheLogin)
unless ( $this->{request}->action() eq 'login'
or ( $ENV{REDIRECT_STATUS} || 0 ) >= 400 )
{
$cgis->clear('VALID_ACTIONS')
unless $this->{request}->action() eq 'login';
}
}

# Add a new validation key to the set for this session
sub _addValidationKey {
my ( $this, $form ) = @_;
my $cgis = $this->{users}->{loginManager}->{_cgisession};
my $actions = $cgis->param('VALID_ACTIONS');
$actions ||= {};
my $nonce = $this->_getValidationKey($form);
$actions->{$nonce} = $form;
$cgis->param( 'VALID_ACTIONS', $actions );
return "$form<input type='hidden' name='validation_key' value='$nonce' />";
}

=begin TML
---++ ObjectMethod checkValidationKey( $key ) -> $boolean
---++ ObjectMethod getCGISession() -> $cgisession
Check that the given validation key is valid for the current session.
Get the CGI::Session object associated with this session, if there is
one. May return undef.
=cut

sub checkValidationKey {
my ( $this, $nonce ) = @_;
my $cgis = $this->{users}->{loginManager}->{_cgisession};
my $actions = $cgis->param('VALID_ACTIONS');
return 0 unless ref($actions) eq 'HASH';
return $actions->{$nonce};

sub getCGISession {
my $this = shift;
return $this->{users}->{loginManager}->{_cgisession};
}

=begin TML
Expand Down
4 changes: 4 additions & 0 deletions core/lib/Foswiki.spec
Expand Up @@ -1190,6 +1190,10 @@ $Foswiki::cfg{ReplaceIfEditedAgainWithin} = 3600;
# can always be broken, but they are valuable if you want to avoid merge
# conflicts (e.g. you use highly structured data in your topic text and
# want to avoid ever having to deal with conflicts)
# <p />Since Foswiki 1.0.6, Foswiki pages that can be used to POST to the
# server have a validation key, that must be sent to the server for the
# post to succeed. These validation keys can only be used once, and expire
# at the same time as the lease expires.
$Foswiki::cfg{LeaseLength} = 3600;
# **NUMBER EXPERT**
Expand Down
3 changes: 2 additions & 1 deletion core/lib/Foswiki/AccessControlException.pm
Expand Up @@ -43,7 +43,8 @@ the function or parameter.
# AND ENSURE ALL POD DOCUMENTATION IS COMPLETE AND ACCURATE.

package Foswiki::AccessControlException;
use base 'Error';
use Error ();
@Foswiki::AccessControlException::ISA = ( 'Error' );

use strict;

Expand Down
2 changes: 2 additions & 0 deletions core/lib/Foswiki/Contrib/core/MANIFEST
Expand Up @@ -611,6 +611,7 @@ lib/Foswiki/Users/ApacheHtpasswdUser.pm 0444
lib/Foswiki/Users/BaseUserMapping.pm 0444
lib/Foswiki/Users/HtPasswdUser.pm 0444
lib/Foswiki/Users/Password.pm 0444
lib/Foswiki/Validation.pm 0444
lib/MANIFEST 0444
lib/Monitor.pm 0444
locale/Foswiki.pot 0444
Expand Down Expand Up @@ -1030,6 +1031,7 @@ templates/searchbookview.tmpl 0444
templates/searchformat.tmpl 0444
templates/searchmeta.tmpl 0444
templates/settings.tmpl 0444
templates/validate.tmpl 0444
templates/view.plain.tmpl 0444
templates/view.print.tmpl 0444
templates/view.rss.tmpl 0444
Expand Down
1 change: 0 additions & 1 deletion core/lib/Foswiki/Form/FieldDefinition.pm
Expand Up @@ -325,7 +325,6 @@ The value is protected by Foswiki::Render::protectFormFieldValue.

sub renderForDisplay {
my ( $this, $format, $value, $attrs ) = @_;
ASSERT( !$attrs || ref($attrs) eq 'HASH' ) if DEBUG;

if ( !$attrs->{showhidden} ) {
my $fa = $this->{attributes} || '';
Expand Down
5 changes: 3 additions & 2 deletions core/lib/Foswiki/LoginManager.pm
Expand Up @@ -997,8 +997,9 @@ $Foswiki::cfg{LoginNameFilterIn}

sub isValidLoginName {
my ($this, $name) = @_;
ASSERT(! ref($name) ) if DEBUG; #this function was erroniously marked as static

# this function was erroneously marked as static
ASSERT(! ref($name) ) if DEBUG;

return $name =~ /$Foswiki::cfg{LoginNameFilterIn}/;
}

Expand Down
59 changes: 44 additions & 15 deletions core/lib/Foswiki/UI.pm
Expand Up @@ -8,6 +8,7 @@ Coordinator of execution flow and service functions used by the UI packages
=cut

package Foswiki::UI;

use strict;

BEGIN {
Expand Down Expand Up @@ -142,13 +143,16 @@ BEGIN {

use Error qw(:try);
use Assert;
use CGI ();

use Foswiki;
use Foswiki::Request;
use Foswiki::Response;
use Foswiki::OopsException;
use Foswiki::EngineException;
use CGI;
use Foswiki ();
use Foswiki::Request ();
use Foswiki::Response ();
use Foswiki::OopsException ();
use Foswiki::EngineException ();
use Foswiki::ValidationException ();
use Foswiki::AccessControlException ();
use Foswiki::Validation ();

# Used to lazily load UI handler modules
our %isInitialized = ();
Expand Down Expand Up @@ -238,6 +242,9 @@ sub handleRequest {
if TRACE_PASSTHRU;
}
}
#print STDERR "INCOMING ".$req->method()." ".$req->url." -> ".$sub."\n";
#require Data::Dumper;
#print STDERR Data::Dumper->Dump([$req]);
if ( UNIVERSAL::isa( $Foswiki::engine, 'Foswiki::Engine::CLI' ) ) {
$dispatcher->{context}->{command_line} = 1;
} elsif ( defined $req->method()
Expand Down Expand Up @@ -286,6 +293,20 @@ sub _execute {
$session->{users}->{loginManager}->checkAccess();
&$sub($session);
}
catch Foswiki::ValidationException with {
my $query = $session->{request};
# Redirect with passthrough so we don't lose the
# original query params. We use the login script for
# validation because it already has the correct criteria
# in httpd.conf for Apache login.
my $url = $session->getScriptUrl(
0, 'login', $session->{webName}, $session->{topicName} );
$query->param( -name => 'action',
-value => 'validate' );
$query->param( -name => 'origurl',
-value => $session->{request}->uri );
$session->redirect( $url, 1 ); # with passthrough
}
catch Foswiki::AccessControlException with {
my $e = shift;
unless ( $session->{users}->{loginManager}->forceAuthentication() )
Expand Down Expand Up @@ -370,7 +391,14 @@ Handler to "logon" action.

sub logon {
my $session = shift;
$session->{users}->{loginManager}->login( $session->{request}, $session );
if (($session->{request}->param('action') ||'') eq 'validate'
# Force login if not recognisably authenticated
&& $session->inContext('authenticated')) {
Foswiki::Validation::validate( $session );
} else {
$session->{users}->{loginManager}->login(
$session->{request}, $session );
}
}

=begin TML
Expand Down Expand Up @@ -476,22 +504,23 @@ sub readTemplateTopic {

=pod TML
---++ StaticMethod checkValidationKey( $session, $web, $topic )
---++ StaticMethod checkValidationKey( $session )
Check the validation key for the given action.
Check the validation key for the given action. Throws an exception
if the validation key isn't valid (handled in _execute(), above)
See Foswiki::Validation for more information.
=cut

sub checkValidationKey {
my ($session, $script, $web, $topic) = @_;
my ($session) = @_;

# Check the nonce before we do anything else
my $nonce = $session->{request}->param('validation_key');
if (!defined($nonce) || !$session->checkValidationKey($nonce)) {
throw Foswiki::AccessControlException(
$script, $session->{user},
$web, $topic, 'Expired or invalid validation key'
);
if (!defined($nonce) || !Foswiki::Validation::isValidNonce(
$session->getCGISession(), $nonce)) {
throw Foswiki::ValidationException();
}
}

Expand Down
5 changes: 3 additions & 2 deletions core/lib/Foswiki/UI/Save.pm
Expand Up @@ -457,6 +457,9 @@ WARN
return;
}

# Do this *before* we do any query parameter rewriting
Foswiki::UI::checkValidationKey($session, 'save', $web, $topic);

my $editaction = lc( $query->param('editaction') ) || '';
my $edit = $query->param('edit') || 'edit';
my $editparams = $query->param('editparams') || '';
Expand Down Expand Up @@ -545,8 +548,6 @@ WARN

#success - redirect to topic view (unless its a checkpoint save)

Foswiki::UI::checkValidationKey($session, 'save', $web, $topic);

if ( $saveCmd eq 'delRev' ) {

# delete top revision
Expand Down

0 comments on commit 036ef03

Please sign in to comment.