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
|
||||
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,
|
||||
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.
|
||||
## multiple accessors of wiki source directory
|
||||
|
||||
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
|
||||
|
||||
|
@ -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.
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
161
ikiwiki
161
ikiwiki
|
@ -127,7 +127,7 @@ sub writefile ($$) { #{{{
|
|||
close OUT;
|
||||
} #}}}
|
||||
|
||||
sub findlinks { #{{{
|
||||
sub findlinks ($) { #{{{
|
||||
my $content=shift;
|
||||
|
||||
my @links;
|
||||
|
@ -243,6 +243,10 @@ sub linkbacks ($$) { #{{{
|
|||
return $content;
|
||||
} #}}}
|
||||
|
||||
sub indexlink () { #{{{
|
||||
return "<a href=\"$url\">$wikiname</a>/ ";
|
||||
} #}}}
|
||||
|
||||
sub finalize ($$) { #{{{
|
||||
my $content=shift;
|
||||
my $page=shift;
|
||||
|
@ -262,7 +266,7 @@ sub finalize ($$) { #{{{
|
|||
$path.="../";
|
||||
}
|
||||
$path=~s/\.\.\/$/index.html/;
|
||||
$pagelink="<a href=\"$path\">$wikiname</a>/ $pagelink";
|
||||
$pagelink=indexlink()." $pagelink";
|
||||
|
||||
my @actions;
|
||||
if (length $cgiurl) {
|
||||
|
@ -312,7 +316,7 @@ sub render ($) { #{{{
|
|||
} #}}}
|
||||
|
||||
sub loadindex () { #{{{
|
||||
open (IN, "$srcdir/.index") || return;
|
||||
open (IN, "$srcdir/.ikiwiki/index") || return;
|
||||
while (<IN>) {
|
||||
$_=possibly_foolish_untaint($_);
|
||||
chomp;
|
||||
|
@ -328,7 +332,10 @@ sub loadindex () { #{{{
|
|||
} #}}}
|
||||
|
||||
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) {
|
||||
print OUT "$oldpagemtime{$page} $pagesources{$page} $renderedfiles{$page} ".
|
||||
join(" ", @{$links{$page}})."\n"
|
||||
|
@ -589,7 +596,8 @@ sub gen_wrapper ($$) { #{{{
|
|||
|
||||
my @envsave;
|
||||
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="";
|
||||
foreach my $var (@envsave) {
|
||||
$envsave.=<<"EOF"
|
||||
|
@ -638,16 +646,9 @@ EOF
|
|||
exit 0;
|
||||
} #}}}
|
||||
|
||||
sub cgi () { #{{{
|
||||
eval q{use CGI};
|
||||
my $q=CGI->new;
|
||||
sub cgi_recentchanges ($) { #{{{
|
||||
my $q=shift;
|
||||
|
||||
my $do=$q->param('do');
|
||||
if (! defined $do || ! length $do) {
|
||||
error("\"do\" parameter missing");
|
||||
}
|
||||
|
||||
if ($do eq 'recentchanges') {
|
||||
my $list="<ul>\n";
|
||||
foreach my $change (rcs_recentchanges(100)) {
|
||||
$list.="<li>";
|
||||
|
@ -662,10 +663,136 @@ sub cgi () { #{{{
|
|||
|
||||
print $q->header,
|
||||
$q->start_html("RecentChanges"),
|
||||
$q->h1("<a href=\"$url\">$wikiname</a>/ 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 () { #{{{
|
||||
eval q{use CGI};
|
||||
eval q{use CGI::Session};
|
||||
|
||||
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');
|
||||
if (! defined $do || ! length $do) {
|
||||
error("\"do\" parameter missing");
|
||||
}
|
||||
|
||||
if ($do eq 'recentchanges') {
|
||||
cgi_recentchanges($q);
|
||||
return;
|
||||
}
|
||||
|
||||
if (! defined $session->param("name") || $do eq 'signin') {
|
||||
cgi_signin($q, $session);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -706,7 +833,7 @@ sub cgi () { #{{{
|
|||
$q->param("do", "save");
|
||||
print $q->header,
|
||||
$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->hidden('do'),
|
||||
"Select page location:",
|
||||
|
@ -733,7 +860,7 @@ sub cgi () { #{{{
|
|||
$q->param("do", "save");
|
||||
print $q->header,
|
||||
$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->hidden('do'),
|
||||
$q->hidden('page'),
|
||||
|
|
Loading…
Reference in New Issue