added signin form, although it needs to be hooked up to a user store
parent
e7f2e59d1b
commit
1311d67f0d
|
@ -48,9 +48,14 @@ TODO: seems that locking to prevent more than one ikiwiki run at a time
|
||||||
would both fix this and is a good idea in general. With locking, an
|
would both fix this and is a good idea in general. With locking, an
|
||||||
attacker couldn't get ikiwiki to svn up while another instance was running.
|
attacker couldn't get ikiwiki to svn up while another instance was running.
|
||||||
|
|
||||||
Even with locking, if an attacker has local write access to the checkout,
|
## multiple accessors of wiki source directory
|
||||||
they could still fool ikiwiki using similar races. So it's best if only one
|
|
||||||
person can ever write to the checkout that ikiwiki compiles the moo from.
|
If multiple people can write to the source directory ikiwiki is using, then
|
||||||
|
one can cause trouble for the other when they run ikiwiki through symlink
|
||||||
|
sttacks.
|
||||||
|
|
||||||
|
So it's best if only one person can ever write to the checkout that ikiwiki
|
||||||
|
compiles the moo from.
|
||||||
|
|
||||||
## webserver symlink attacks
|
## webserver symlink attacks
|
||||||
|
|
||||||
|
@ -58,7 +63,7 @@ If someone checks in a symlink to /etc/passwd, ikiwiki would publish that.
|
||||||
To aoid this, ikiwiki will need to avoid reading files that are symlinks.
|
To aoid this, ikiwiki will need to avoid reading files that are symlinks.
|
||||||
TODO and note discussion of races above.
|
TODO and note discussion of races above.
|
||||||
|
|
||||||
## cgi security
|
## cgi data security
|
||||||
|
|
||||||
When ikiwiki runs as a cgi to edit a page, it is passed the name of the
|
When ikiwiki runs as a cgi to edit a page, it is passed the name of the
|
||||||
page to edit. It has to make sure to sanitise this page, to prevent eg,
|
page to edit. It has to make sure to sanitise this page, to prevent eg,
|
||||||
|
@ -68,6 +73,21 @@ removing unallowed characters, then making sure it doesn't start with "/"
|
||||||
or contain ".." or "/.svn/". Annoyingly ad-hoc, this kind of code is where
|
or contain ".." or "/.svn/". Annoyingly ad-hoc, this kind of code is where
|
||||||
security holes breed. It needs a test suite at the very least.
|
security holes breed. It needs a test suite at the very least.
|
||||||
|
|
||||||
|
## cgi password security
|
||||||
|
|
||||||
|
Login to the wiki involves sending a password in cleartext over the net.
|
||||||
|
Cracking the password only allows editing the moo as that user though.
|
||||||
|
If you care, you can use https, I suppose.
|
||||||
|
|
||||||
|
## CGI::Session security
|
||||||
|
|
||||||
|
Is CGI::Session secure? Well, it writes the session files world-readable,
|
||||||
|
which could be used by a local attacker to take over someone's session.
|
||||||
|
|
||||||
|
I have no idea if CGI::Session writes session files securely to /tmp.
|
||||||
|
ikiwiki makes it write them to a directory it controls (but see "multiple
|
||||||
|
accessors of wiki source directory" above).
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
# Probable non-holes
|
# Probable non-holes
|
||||||
|
|
175
ikiwiki
175
ikiwiki
|
@ -127,7 +127,7 @@ sub writefile ($$) { #{{{
|
||||||
close OUT;
|
close OUT;
|
||||||
} #}}}
|
} #}}}
|
||||||
|
|
||||||
sub findlinks { #{{{
|
sub findlinks ($) { #{{{
|
||||||
my $content=shift;
|
my $content=shift;
|
||||||
|
|
||||||
my @links;
|
my @links;
|
||||||
|
@ -243,6 +243,10 @@ sub linkbacks ($$) { #{{{
|
||||||
return $content;
|
return $content;
|
||||||
} #}}}
|
} #}}}
|
||||||
|
|
||||||
|
sub indexlink () { #{{{
|
||||||
|
return "<a href=\"$url\">$wikiname</a>/ ";
|
||||||
|
} #}}}
|
||||||
|
|
||||||
sub finalize ($$) { #{{{
|
sub finalize ($$) { #{{{
|
||||||
my $content=shift;
|
my $content=shift;
|
||||||
my $page=shift;
|
my $page=shift;
|
||||||
|
@ -262,7 +266,7 @@ sub finalize ($$) { #{{{
|
||||||
$path.="../";
|
$path.="../";
|
||||||
}
|
}
|
||||||
$path=~s/\.\.\/$/index.html/;
|
$path=~s/\.\.\/$/index.html/;
|
||||||
$pagelink="<a href=\"$path\">$wikiname</a>/ $pagelink";
|
$pagelink=indexlink()." $pagelink";
|
||||||
|
|
||||||
my @actions;
|
my @actions;
|
||||||
if (length $cgiurl) {
|
if (length $cgiurl) {
|
||||||
|
@ -312,7 +316,7 @@ sub render ($) { #{{{
|
||||||
} #}}}
|
} #}}}
|
||||||
|
|
||||||
sub loadindex () { #{{{
|
sub loadindex () { #{{{
|
||||||
open (IN, "$srcdir/.index") || return;
|
open (IN, "$srcdir/.ikiwiki/index") || return;
|
||||||
while (<IN>) {
|
while (<IN>) {
|
||||||
$_=possibly_foolish_untaint($_);
|
$_=possibly_foolish_untaint($_);
|
||||||
chomp;
|
chomp;
|
||||||
|
@ -328,7 +332,10 @@ sub loadindex () { #{{{
|
||||||
} #}}}
|
} #}}}
|
||||||
|
|
||||||
sub saveindex () { #{{{
|
sub saveindex () { #{{{
|
||||||
open (OUT, ">$srcdir/.index") || error("cannot write to .index: $!");
|
if (! -d "$srcdir/.ikiwiki") {
|
||||||
|
mkdir("$srcdir/.ikiwiki");
|
||||||
|
}
|
||||||
|
open (OUT, ">$srcdir/.ikiwiki/index") || error("cannot write to index: $!");
|
||||||
foreach my $page (keys %oldpagemtime) {
|
foreach my $page (keys %oldpagemtime) {
|
||||||
print OUT "$oldpagemtime{$page} $pagesources{$page} $renderedfiles{$page} ".
|
print OUT "$oldpagemtime{$page} $pagesources{$page} $renderedfiles{$page} ".
|
||||||
join(" ", @{$links{$page}})."\n"
|
join(" ", @{$links{$page}})."\n"
|
||||||
|
@ -589,7 +596,8 @@ sub gen_wrapper ($$) { #{{{
|
||||||
|
|
||||||
my @envsave;
|
my @envsave;
|
||||||
push @envsave, qw{REMOTE_ADDR QUERY_STRING REQUEST_METHOD REQUEST_URI
|
push @envsave, qw{REMOTE_ADDR QUERY_STRING REQUEST_METHOD REQUEST_URI
|
||||||
CONTENT_TYPE CONTENT_LENGTH GATEWAY_INTERFACE} if $cgi;
|
CONTENT_TYPE CONTENT_LENGTH GATEWAY_INTERFACE
|
||||||
|
HTTP_COOKIE} if $cgi;
|
||||||
my $envsave="";
|
my $envsave="";
|
||||||
foreach my $var (@envsave) {
|
foreach my $var (@envsave) {
|
||||||
$envsave.=<<"EOF"
|
$envsave.=<<"EOF"
|
||||||
|
@ -638,9 +646,140 @@ EOF
|
||||||
exit 0;
|
exit 0;
|
||||||
} #}}}
|
} #}}}
|
||||||
|
|
||||||
|
sub cgi_recentchanges ($) { #{{{
|
||||||
|
my $q=shift;
|
||||||
|
|
||||||
|
my $list="<ul>\n";
|
||||||
|
foreach my $change (rcs_recentchanges(100)) {
|
||||||
|
$list.="<li>";
|
||||||
|
$list.=join(", ", map { htmllink("", $_, 1) } @{$change->{pages}});
|
||||||
|
$list.="<br>\n";
|
||||||
|
$list.="changed ".$change->{when}." by ".
|
||||||
|
htmllink("", $change->{user}, 1).
|
||||||
|
": <i>".$change->{message}."</i>\n";
|
||||||
|
$list.="</li>\n";
|
||||||
|
}
|
||||||
|
$list.="</ul>\n";
|
||||||
|
|
||||||
|
print $q->header,
|
||||||
|
$q->start_html("RecentChanges"),
|
||||||
|
$q->h1(indexlink()." RecentChanges"),
|
||||||
|
$list,
|
||||||
|
$q->end_form,
|
||||||
|
$q->end_html;
|
||||||
|
} #}}}
|
||||||
|
|
||||||
|
sub cgi_signin ($$) { #{{{
|
||||||
|
my $q=shift;
|
||||||
|
my $session=shift;
|
||||||
|
|
||||||
|
eval q{use CGI::FormBuilder};
|
||||||
|
my $form = CGI::FormBuilder->new(
|
||||||
|
title => "$wikiname signin",
|
||||||
|
fields => [qw(do page name password confirm_password email)],
|
||||||
|
header => 1,
|
||||||
|
method => 'POST',
|
||||||
|
validate => {
|
||||||
|
name => '/^\w+$/',
|
||||||
|
confirm_password => {
|
||||||
|
perl => q{eq $form->field("password")},
|
||||||
|
},
|
||||||
|
email => 'EMAIL',
|
||||||
|
},
|
||||||
|
required => 'NONE',
|
||||||
|
javascript => 0,
|
||||||
|
params => $q,
|
||||||
|
action => $q->request_uri,
|
||||||
|
);
|
||||||
|
|
||||||
|
$form->sessionid($session->id);
|
||||||
|
$form->field(name => "name", required => 0);
|
||||||
|
$form->field(name => "do", type => "hidden");
|
||||||
|
$form->field(name => "page", type => "hidden");
|
||||||
|
$form->field(name => "password", type => "password", required => 0);
|
||||||
|
$form->field(name => "confirm_password", type => "password", required => 0);
|
||||||
|
$form->field(name => "email", required => 0);
|
||||||
|
if ($session->param("name")) {
|
||||||
|
$form->field(name => "name", value => $session->param("name"));
|
||||||
|
}
|
||||||
|
if ($q->param("do") ne "signin") {
|
||||||
|
$form->text("You need to log in before you can edit pages.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($form->submitted) {
|
||||||
|
# Set required fields based on how form was submitted.
|
||||||
|
my %required=(
|
||||||
|
"Login" => [qw(name password)],
|
||||||
|
"Register" => [qw(name password confirm_password email)],
|
||||||
|
"Mail Password" => [qw(name)],
|
||||||
|
);
|
||||||
|
foreach my $opt (@{$required{$form->submitted}}) {
|
||||||
|
$form->field(name => $opt, required => 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate password differently depending on how form was
|
||||||
|
# submitted.
|
||||||
|
if ($form->submitted eq 'Login') {
|
||||||
|
$form->field(
|
||||||
|
name => "password",
|
||||||
|
validate => sub {
|
||||||
|
# TODO get real user password
|
||||||
|
shift eq "foo";
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$form->field(name => "password", validate => 'VALUE');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# Comments only shown first time.
|
||||||
|
$form->field(name => "name", comment => "use FirstnameLastName");
|
||||||
|
$form->field(name => "confirm_password", comment => "(only needed");
|
||||||
|
$form->field(name => "email", comment => "for registration)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($form->submitted && $form->validate) {
|
||||||
|
if ($form->submitted eq 'Login') {
|
||||||
|
$session->param("name", $form->field("name"));
|
||||||
|
if (defined $form->field("do")) {
|
||||||
|
$q->redirect(
|
||||||
|
"$cgiurl?do=".$form->field("do").
|
||||||
|
"&page=".$form->field("page"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$q->redirect($url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elsif ($form->submitted eq 'Register') {
|
||||||
|
# TODO: save registration info
|
||||||
|
$form->field(name => "confirm_password", type => "hidden");
|
||||||
|
$form->field(name => "email", type => "hidden");
|
||||||
|
$form->text("Registration successful. Now you can Login.");
|
||||||
|
print $form->render(submit => ["Login"]);;
|
||||||
|
}
|
||||||
|
elsif ($form->submitted eq 'Mail Password') {
|
||||||
|
# TODO mail password
|
||||||
|
$form->text("Your password has been emailed to you.");
|
||||||
|
print $form->render(submit => ["Login", "Register", "Mail Password"]);;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print $form->render(submit => ["Login", "Register", "Mail Password"]);;
|
||||||
|
}
|
||||||
|
} #}}}
|
||||||
|
|
||||||
sub cgi () { #{{{
|
sub cgi () { #{{{
|
||||||
eval q{use CGI};
|
eval q{use CGI};
|
||||||
|
eval q{use CGI::Session};
|
||||||
|
|
||||||
my $q=CGI->new;
|
my $q=CGI->new;
|
||||||
|
# session id has to be _sessionid for CGI::FormBuilder to work.
|
||||||
|
# TODO: stop having the formbuilder emit cookies and change session
|
||||||
|
# id to something else.
|
||||||
|
CGI::Session->name("_sessionid");
|
||||||
|
my $session = CGI::Session->new(undef, $q,
|
||||||
|
{ Directory=> "$srcdir/.ikiwiki/sessions" });
|
||||||
|
|
||||||
my $do=$q->param('do');
|
my $do=$q->param('do');
|
||||||
if (! defined $do || ! length $do) {
|
if (! defined $do || ! length $do) {
|
||||||
|
@ -648,24 +787,12 @@ sub cgi () { #{{{
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($do eq 'recentchanges') {
|
if ($do eq 'recentchanges') {
|
||||||
my $list="<ul>\n";
|
cgi_recentchanges($q);
|
||||||
foreach my $change (rcs_recentchanges(100)) {
|
return;
|
||||||
$list.="<li>";
|
}
|
||||||
$list.=join(", ", map { htmllink("", $_, 1) } @{$change->{pages}});
|
|
||||||
$list.="<br>\n";
|
|
||||||
$list.="changed ".$change->{when}." by ".
|
|
||||||
htmllink("", $change->{user}, 1).
|
|
||||||
": <i>".$change->{message}."</i>\n";
|
|
||||||
$list.="</li>\n";
|
|
||||||
}
|
|
||||||
$list.="</ul>\n";
|
|
||||||
|
|
||||||
print $q->header,
|
if (! defined $session->param("name") || $do eq 'signin') {
|
||||||
$q->start_html("RecentChanges"),
|
cgi_signin($q, $session);
|
||||||
$q->h1("<a href=\"$url\">$wikiname</a>/ RecentChanges"),
|
|
||||||
$list,
|
|
||||||
$q->end_form,
|
|
||||||
$q->end_html;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -706,7 +833,7 @@ sub cgi () { #{{{
|
||||||
$q->param("do", "save");
|
$q->param("do", "save");
|
||||||
print $q->header,
|
print $q->header,
|
||||||
$q->start_html("Creating $page"),
|
$q->start_html("Creating $page"),
|
||||||
$q->h1("<a href=\"$url\">$wikiname</a>/ Creating $page"),
|
$q->h1(indexlink()." Creating $page"),
|
||||||
$q->start_form(-action => $action),
|
$q->start_form(-action => $action),
|
||||||
$q->hidden('do'),
|
$q->hidden('do'),
|
||||||
"Select page location:",
|
"Select page location:",
|
||||||
|
@ -733,7 +860,7 @@ sub cgi () { #{{{
|
||||||
$q->param("do", "save");
|
$q->param("do", "save");
|
||||||
print $q->header,
|
print $q->header,
|
||||||
$q->start_html("Editing $page"),
|
$q->start_html("Editing $page"),
|
||||||
$q->h1("<a href=\"$url\">$wikiname</a>/ Editing $page"),
|
$q->h1(indexlink()." Editing $page"),
|
||||||
$q->start_form(-action => $action),
|
$q->start_form(-action => $action),
|
||||||
$q->hidden('do'),
|
$q->hidden('do'),
|
||||||
$q->hidden('page'),
|
$q->hidden('page'),
|
||||||
|
|
Loading…
Reference in New Issue