* Add "last" parameter to hook function. Very basic ordering, and hopefully

nothing more spohisticated will be needed.
* Add formbuilder_setup and formbuilder hooks.
* Split out a passwordauth module, that holds all the traditional password
  based authentication etc code. It's enabled by default, but can be disabled
  if you want only openid or some other auth method.
master
joey 2006-11-20 20:37:27 +00:00
parent 74cb86fbb7
commit 3e593eb9c0
9 changed files with 178 additions and 223 deletions

View File

@ -62,7 +62,7 @@ sub defaultconfig () { #{{{
setup => undef, setup => undef,
adminuser => undef, adminuser => undef,
adminemail => undef, adminemail => undef,
plugin => [qw{mdwn inline htmlscrubber}], plugin => [qw{mdwn inline htmlscrubber passwordauth}],
timeformat => '%c', timeformat => '%c',
locale => undef, locale => undef,
sslcookie => 0, sslcookie => 0,
@ -663,7 +663,15 @@ sub run_hooks ($$) { # {{{
my $sub=shift; my $sub=shift;
if (exists $hooks{$type}) { if (exists $hooks{$type}) {
my @deferred;
foreach my $id (keys %{$hooks{$type}}) { foreach my $id (keys %{$hooks{$type}}) {
if ($hooks{$type}{$id}{last}) {
push @deferred, $id;
next;
}
$sub->($hooks{$type}{$id}{call});
}
foreach my $id (@deferred) {
$sub->($hooks{$type}{$id}{call}); $sub->($hooks{$type}{$id}{call});
} }
} }

View File

@ -127,16 +127,9 @@ sub cgi_signin ($$) { #{{{
error($@) if $@; error($@) if $@;
my $form = CGI::FormBuilder->new( my $form = CGI::FormBuilder->new(
title => "signin", title => "signin",
fields => [qw(do name password openid_url)],
header => 1, header => 1,
charset => "utf-8", charset => "utf-8",
method => 'POST', method => 'POST',
validate => {
confirm_password => {
perl => q{eq $form->field("password")},
},
email => 'EMAIL',
},
required => 'NONE', required => 'NONE',
javascript => 0, javascript => 0,
params => $q, params => $q,
@ -146,170 +139,32 @@ sub cgi_signin ($$) { #{{{
{template_params("signin.tmpl")} : ""), {template_params("signin.tmpl")} : ""),
stylesheet => baseurl()."style.css", stylesheet => baseurl()."style.css",
); );
my $buttons=["Login"];
decode_form_utf8($form);
$form->field(name => "name", required => 0, size => 30);
$form->field(name => "do", type => "hidden"); $form->field(name => "do", type => "hidden");
$form->field(name => "password", type => "password", required => 0);
if ($config{openid}) {
$form->field(name => "openid_url", label => "OpenID", size => 30,
comment => '('.
htmllink("", "", "OpenID", 1, 0, "What's this?")
.($config{openidsignup} ? " | <a href=\"$config{openidsignup}\">Get an OpenID</a>" : "")
.')');
}
else {
$form->field(name => "openid_url", type => "hidden");
}
if ($form->submitted eq "Register" || $form->submitted eq "Create Account") {
$form->title("register");
$form->text("");
$form->fields(qw(do name password confirm_password email));
$form->field(name => "confirm_password", type => "password");
$form->field(name => "email", type => "text");
$form->field(name => "openid_url", type => "hidden");
}
if ($q->param("do") ne "signin" && !$form->submitted) { if ($q->param("do") ne "signin" && !$form->submitted) {
$form->text("You need to log in first."); $form->text("You need to log in first.");
} }
if ($form->submitted) { run_hooks(formbuilder_setup => sub {
my $submittype=$form->submitted; shift->(form => $form, cgi => $q, session => $session);
# OpenID login uses the Login button, but validates });
# differently.
if ($submittype eq "Login" && $config{openid} &&
length $form->field("openid_url")) {
$submittype="OpenID";
$form->field(
name => "openid_url",
validate => sub {
# FIXME: ugh
IkiWiki::Plugin::openid::validate($q, $session, shift, $form);
},
);
}
# Set required fields based on how form was submitted.
my %required=(
"Login" => [qw(name password)],
"Register" => [],
"Create Account" => [qw(name password confirm_password email)],
"Mail Password" => [qw(name)],
"OpenID" => [qw(openid_url)],
);
foreach my $opt (@{$required{$submittype}}) {
$form->field(name => $opt, required => 1);
}
# Validate password differently depending on how decode_form_utf8($form);
# form was submitted.
if ($submittype eq 'Login') {
$form->field(
name => "password",
validate => sub {
length $form->field("name") &&
shift eq userinfo_get($form->field("name"), 'password');
},
);
$form->field(name => "name", validate => '/^\w+$/');
}
elsif ($submittype ne 'OpenID') {
$form->field(name => "password", validate => 'VALUE');
}
# And make sure the entered name exists when logging
# in or sending email, and does not when registering.
if ($submittype eq 'Create Account' ||
$submittype eq 'Register') {
$form->field(
name => "name",
validate => sub {
my $name=shift;
length $name &&
$name=~/$config{wiki_file_regexp}/ &&
! userinfo_get($name, "regdate");
},
);
}
elsif ($submittype ne 'OpenID') {
$form->field(
name => "name",
validate => sub {
my $name=shift;
length $name &&
userinfo_get($name, "regdate");
},
);
}
}
else {
# First time settings.
$form->field(name => "name", comment => "use FirstnameLastName");
if ($session->param("name")) {
$form->field(name => "name", value => $session->param("name"));
}
}
if ($form->submitted && $form->validate) { if (exists $hooks{formbuilder}) {
if ($form->submitted eq 'Login') { run_hooks(formbuilder => sub {
$session->param("name", $form->field("name")); shift->(form => $form, cgi => $q, session => $session,
cgi_postsignin($q, $session); buttons => $buttons);
} });
elsif ($form->submitted eq 'Create Account') {
my $user_name=$form->field('name');
if (userinfo_setall($user_name, {
'email' => $form->field('email'),
'password' => $form->field('password'),
'regdate' => time
})) {
$form->field(name => "confirm_password", type => "hidden");
$form->field(name => "email", type => "hidden");
$form->text("Account creation successful. Now you can Login.");
printheader($session);
print misctemplate($form->title, $form->render(submit => ["Login"]));
}
else {
error("Error creating account.");
}
}
elsif ($form->submitted eq 'Mail Password') {
my $user_name=$form->field("name");
my $template=template("passwordmail.tmpl");
$template->param(
user_name => $user_name,
user_password => userinfo_get($user_name, "password"),
wikiurl => $config{url},
wikiname => $config{wikiname},
REMOTE_ADDR => $ENV{REMOTE_ADDR},
);
eval q{use Mail::Sendmail};
error($@) if $@;
sendmail(
To => userinfo_get($user_name, "email"),
From => "$config{wikiname} admin <$config{adminemail}>",
Subject => "$config{wikiname} information",
Message => $template->output,
) or error("Failed to send mail");
$form->text("Your password has been emailed to you.");
$form->field(name => "name", required => 0);
printheader($session);
print misctemplate($form->title, $form->render(submit => ["Login", "Mail Password"]));
}
elsif ($form->submitted eq "Register") {
printheader($session);
print misctemplate($form->title, $form->render(submit => ["Create Account"]));
}
}
elsif ($form->submitted eq "Create Account") {
printheader($session);
print misctemplate($form->title, $form->render(submit => ["Create Account"]));
} }
else { else {
if ($form->submitted) {
$form->validate;
}
printheader($session); printheader($session);
print misctemplate($form->title, $form->render(submit => ["Login", "Register", "Mail Password"])); print misctemplate($form->title, $form->render(submit => $buttons));
} }
} #}}} } #}}}
@ -338,15 +193,10 @@ sub cgi_prefs ($$) { #{{{
error($@) if $@; error($@) if $@;
my $form = CGI::FormBuilder->new( my $form = CGI::FormBuilder->new(
title => "preferences", title => "preferences",
fields => [qw(do name password confirm_password email
subscriptions locked_pages)],
header => 0, header => 0,
charset => "utf-8", charset => "utf-8",
method => 'POST', method => 'POST',
validate => { validate => {
confirm_password => {
perl => q{eq $form->field("password")},
},
email => 'EMAIL', email => 'EMAIL',
}, },
required => 'NONE', required => 'NONE',
@ -357,30 +207,26 @@ sub cgi_prefs ($$) { #{{{
{template_params("prefs.tmpl")} : ""), {template_params("prefs.tmpl")} : ""),
stylesheet => baseurl()."style.css", stylesheet => baseurl()."style.css",
); );
my @buttons=("Save Preferences", "Logout", "Cancel"); my $buttons=["Save Preferences", "Logout", "Cancel"];
run_hooks(formbuilder_setup => sub {
shift->(form => $form, cgi => $q, session => $session);
});
my $user_name=$session->param("name");
$form->field(name => "do", type => "hidden"); $form->field(name => "do", type => "hidden");
$form->field(name => "name", disabled => 1, $form->field(name => "email", size => 50);
value => $user_name, force => 1, size => 30);
$form->field(name => "password", type => "password");
$form->field(name => "confirm_password", type => "password");
$form->field(name => "subscriptions", size => 50, $form->field(name => "subscriptions", size => 50,
comment => "(".htmllink("", "", "PageSpec", 1).")"); comment => "(".htmllink("", "", "PageSpec", 1).")");
$form->field(name => "locked_pages", size => 50, $form->field(name => "locked_pages", size => 50,
comment => "(".htmllink("", "", "PageSpec", 1).")"); comment => "(".htmllink("", "", "PageSpec", 1).")");
$form->field(name => "banned_users", size => 50); $form->field(name => "banned_users", size => 50);
my $user_name=$session->param("name");
if (! is_admin($user_name)) { if (! is_admin($user_name)) {
$form->field(name => "locked_pages", type => "hidden"); $form->field(name => "locked_pages", type => "hidden");
$form->field(name => "banned_users", type => "hidden"); $form->field(name => "banned_users", type => "hidden");
} }
if ($config{httpauth}) {
$form->field(name => "password", type => "hidden");
$form->field(name => "confirm_password", type => "hidden");
}
if (! $form->submitted) { if (! $form->submitted) {
$form->field(name => "email", force => 1, $form->field(name => "email", force => 1,
value => userinfo_get($user_name, "email")); value => userinfo_get($user_name, "email"));
@ -406,8 +252,8 @@ sub cgi_prefs ($$) { #{{{
return; return;
} }
elsif ($form->submitted eq "Save Preferences" && $form->validate) { elsif ($form->submitted eq "Save Preferences" && $form->validate) {
foreach my $field (qw(password email subscriptions locked_pages)) { foreach my $field (qw(email subscriptions locked_pages)) {
if (length $form->field($field)) { if (defined $form->field($field) && length $form->field($field)) {
userinfo_set($user_name, $field, $form->field($field)) || error("failed to set $field"); userinfo_set($user_name, $field, $form->field($field)) || error("failed to set $field");
} }
} }
@ -418,8 +264,16 @@ sub cgi_prefs ($$) { #{{{
$form->text("Preferences saved."); $form->text("Preferences saved.");
} }
printheader($session); if (exists $hooks{formbuilder}) {
print misctemplate($form->title, $form->render(submit => \@buttons)); run_hooks(formbuilder => sub {
shift->(form => $form, cgi => $q, session => $session,
buttons => $buttons);
});
}
else {
printheader($session);
print misctemplate($form->title, $form->render(submit => $buttons));
}
} #}}} } #}}}
sub cgi_editpage ($$) { #{{{ sub cgi_editpage ($$) { #{{{
@ -454,6 +308,10 @@ sub cgi_editpage ($$) { #{{{
template => $renderer, template => $renderer,
); );
run_hooks(formbuilder_setup => sub {
shift->(form => $form, cgi => $q, session => $session);
});
decode_form_utf8($form); decode_form_utf8($form);
# This untaint is safe because titlepage removes any problematic # This untaint is safe because titlepage removes any problematic

View File

@ -8,8 +8,9 @@ use IkiWiki;
sub import { #{{{ sub import { #{{{
hook(type => "getopt", id => "openid", call => \&getopt); hook(type => "getopt", id => "openid", call => \&getopt);
hook(type => "checkconfig", id => "openid", call => \&checkconfig);
hook(type => "auth", id => "openid", call => \&auth); hook(type => "auth", id => "openid", call => \&auth);
hook(type => "formbuilder_setup", id => "openid",
call => \&formbuilder_setup, last => 1);
} # }}} } # }}}
sub getopt () { #{{{ sub getopt () { #{{{
@ -19,37 +20,43 @@ sub getopt () { #{{{
GetOptions("openidsignup=s" => \$config{openidsignup}); GetOptions("openidsignup=s" => \$config{openidsignup});
} #}}} } #}}}
sub checkconfig () { #{{{ sub formbuilder_setup (@) { #{{{
# Currently part of the OpenID code is in CGI.pm, and is enabled by my %params=@_;
# this setting.
# TODO: modularise it all out into this plugin..
$config{openid}=1;
} #}}}
sub auth ($$) { #{{{ my $form=$params{form};
my $q=shift; my $session=$params{session};
my $session=shift; my $cgi=$params{cgi};
if (defined $q->param('openid.mode')) { if ($form->title eq "signin") {
my $csr=getobj($q, $session); $form->field(
name => "openid_url",
label => "OpenID",
size => 30,
comment => '('.
htmllink("", "", "OpenID", 1, 0, "What's this?")
.($config{openidsignup} ? " | <a href=\"$config{openidsignup}\">Get an OpenID</a>" : "")
.')'
);
if (my $setup_url = $csr->user_setup_url) { # Handle submission of an OpenID as validation.
IkiWiki::redirect($q, $setup_url); if ($form->submitted && $form->submitted eq "Login" &&
} defined $form->field("openid_url") &&
elsif ($csr->user_cancel) { length $form->field("openid_url")) {
IkiWiki::redirect($q, $config{url}); $form->field(
} name => "openid_url",
elsif (my $vident = $csr->verified_identity) { validate => sub {
$session->param(name => $vident->url); validate($cgi, $session, shift, $form);
} },
else { );
error("OpenID failure: ".$csr->err); # Skip all other required fields in this case.
foreach my $field ($form->field) {
next if $field eq "openid_url";
$form->field(name => $field, required => 0,
validate => '/.*/');
}
} }
} }
elsif (defined $q->param('openid_identifier')) { }
validate($q, $session, $q->param('openid_identifier'));
}
} #}}}
sub validate ($$$;$) { #{{{ sub validate ($$$;$) { #{{{
my $q=shift; my $q=shift;
@ -77,11 +84,37 @@ sub validate ($$$;$) { #{{{
delayed_return => 1, delayed_return => 1,
); );
# Redirect the user to the OpenID server, which will # Redirect the user to the OpenID server, which will
# eventually bounce them back to auth() above. # eventually bounce them back to auth()
IkiWiki::redirect($q, $check_url); IkiWiki::redirect($q, $check_url);
exit 0; exit 0;
} #}}} } #}}}
sub auth ($$) { #{{{
my $q=shift;
my $session=shift;
if (defined $q->param('openid.mode')) {
my $csr=getobj($q, $session);
if (my $setup_url = $csr->user_setup_url) {
IkiWiki::redirect($q, $setup_url);
}
elsif ($csr->user_cancel) {
IkiWiki::redirect($q, $config{url});
}
elsif (my $vident = $csr->verified_identity) {
$session->param(name => $vident->url);
}
else {
error("OpenID failure: ".$csr->err);
}
}
elsif (defined $q->param('openid_identifier')) {
# myopenid.com affiliate support
validate($q, $session, $q->param('openid_identifier'));
}
} #}}}
sub getobj ($$) { #{{{ sub getobj ($$) { #{{{
my $q=shift; my $q=shift;
my $session=shift; my $session=shift;

View File

@ -21,6 +21,8 @@ sub import { #{{{
hook(type => "change", id => "skeleton", call => \&change); hook(type => "change", id => "skeleton", call => \&change);
hook(type => "cgi", id => "skeleton", call => \&cgi); hook(type => "cgi", id => "skeleton", call => \&cgi);
hook(type => "auth", id => "skeleton", call => \&auth); hook(type => "auth", id => "skeleton", call => \&auth);
hook(type => "formbuilder_setup", id => "skeleton", call => \&formbuilder_setup);
hook(type => "formbuilder", id => "skeleton", call => \&formbuilder);
hook(type => "savestate", id => "savestate", call => \&savestate); hook(type => "savestate", id => "savestate", call => \&savestate);
} # }}} } # }}}
@ -103,6 +105,18 @@ sub auth ($$) { #{{{
debug("skeleton plugin running in auth"); debug("skeleton plugin running in auth");
} #}}} } #}}}
sub formbuilder_setup (@) { #{{{
my %params=@_;
debug("skeleton plugin running in formbuilder_setup");
} # }}}
sub formbuilder (@) { #{{{
my %params=@_;
debug("skeleton plugin running in formbuilder");
} # }}}
sub savestate () { #{{{ sub savestate () { #{{{
debug("skeleton plugin running in savestate"); debug("skeleton plugin running in savestate");
} #}}} } #}}}

8
debian/changelog vendored
View File

@ -14,8 +14,14 @@ ikiwiki (1.34) UNRELEASED; urgency=low
* Add optional "desc" parameter to shortcut definitions. * Add optional "desc" parameter to shortcut definitions.
* Avoid locking the wiki at all when handling some basic cgi stuff * Avoid locking the wiki at all when handling some basic cgi stuff
(searches, recentchanges). (searches, recentchanges).
* Add "last" parameter to hook function. Very basic ordering, and hopefully
nothing more spohisticated will be needed.
* Add formbuilder_setup and formbuilder hooks.
* Split out a passwordauth module, that holds all the traditional password
based authentication etc code. It's enabled by default, but can be disabled
if you want only openid or some other auth method.
-- Joey Hess <joeyh@debian.org> Mon, 20 Nov 2006 06:54:12 -0500 -- Joey Hess <joeyh@debian.org> Mon, 20 Nov 2006 09:17:07 -0500
ikiwiki (1.33) unstable; urgency=low ikiwiki (1.33) unstable; urgency=low

View File

@ -97,7 +97,7 @@ use IkiWiki::Setup::Standard {
# wikitext camelcase pagestats htmltidy fortune # wikitext camelcase pagestats htmltidy fortune
# sidebar map rst toc linkmap openid}], # sidebar map rst toc linkmap openid}],
# If you want to disable any of the default plugins, list them here. # If you want to disable any of the default plugins, list them here.
#disable_plugins => [qw{inline htmlscrubber}], #disable_plugins => [qw{inline htmlscrubber passwordauth}],
# For use with the tag plugin, make all tags be located under a # For use with the tag plugin, make all tags be located under a
# base page. # base page.

View File

@ -7,9 +7,10 @@ wiki, or just have [[type/fun]].
There's documentation if you want to [[write]] your own plugins, or you can There's documentation if you want to [[write]] your own plugins, or you can
install and use plugins contributed by others. install and use plugins contributed by others.
The [[mdwn]], [[inline]], and [[htmlscrubber]] plugins are enabled by default. The [[mdwn]], [[inline]], [[htmlscrubber]], and [[passwordauth]] plugins
To enable other plugins, use the `--plugin` switch described in [[usage]], are enabled by default. To enable other plugins, use the `--plugin` switch
or the equivalent `add_plugins` line in [[ikiwiki.setup]]. described in [[usage]], or the equivalent `add_plugins` line in
[[ikiwiki.setup]].
# Plugin directory # Plugin directory

View File

@ -0,0 +1,9 @@
[[template id=plugin name=passwordauth core=1 included=1 author="[[Joey]]"]]
[[tag type/auth]]
This plugin lets ikiwiki prompt for a user name and password when logging
into the wiki. It also handles registering users, mailing passwords, and
changing passwords in the prefs page.
It is enabled by default, but can be turned off if you want to only use
some other form of authentication, such as [[openid]].

View File

@ -36,6 +36,10 @@ before begining to render pages. This parameter should be set to true if
the hook modifies data in `%links`. Note that doing so will make the hook the hook modifies data in `%links`. Note that doing so will make the hook
be run twice per page build, so avoid doing it for expensive hooks. be run twice per page build, so avoid doing it for expensive hooks.
An optional "last" parameter, if set to a true value, makes the hook run
after all other hooks of its type. Useful if the hook depends on some other
hook being run first.
## Types of hooks ## Types of hooks
In roughly the order they are called. In roughly the order they are called.
@ -119,13 +123,13 @@ return the htmlized content.
hook(type => "pagetemplate", id => "foo", call => \&pagetemplate); hook(type => "pagetemplate", id => "foo", call => \&pagetemplate);
[[Templates]] are filled out for many different things in ikiwiki, [[Templates]] are filled out for many different things in ikiwiki, like
like generating a page, or part of a blog page, or an rss feed, or a cgi. generating a page, or part of a blog page, or an rss feed, or a cgi. This
This hook allows modifying those templates. The function is passed named hook allows modifying those templates. The function is passed named
parameters. The "page" and "destpage" parameters are the same as for a parameters. The "page" and "destpage" parameters are the same as for a
preprocess hook. The "template" parameter is a `HTML::Template` object that preprocess hook. The "template" parameter is a [[cpan HTML::Template]]
is the template that will be used to generate the page. The function can object that is the template that will be used to generate the page. The
manipulate that template object. function can manipulate that template object.
The most common thing to do is probably to call $template->param() to add The most common thing to do is probably to call $template->param() to add
a new custom parameter to the template. a new custom parameter to the template.
@ -177,7 +181,7 @@ terminate the program.
### auth ### auth
hook(type => "cgi", id => "foo", call => \&auth); hook(type => "auth", id => "foo", call => \&auth);
This hook can be used to implement a different authentication method than This hook can be used to implement a different authentication method than
the standard web form. When a user needs to be authenticated, each registered the standard web form. When a user needs to be authenticated, each registered
@ -188,6 +192,28 @@ object's "name" parameter to the authenticated user's name. Note that
if the name is set to the name of a user who is not registered, if the name is set to the name of a user who is not registered,
a basic registration of the user will be automatically performed. a basic registration of the user will be automatically performed.
### formbuilder
hook(type => "formbuilder_setup", id => "foo", call => \&formbuilder_setup);
hook(type => "formbuilder", id => "foo", call => \&formbuilder);
These hooks allow tapping into the parts of ikiwiki that use [[cpan
CGI::FormBuilder]] to generate web forms. These hooks are passed named
parameters: `cgi`, `session`, and `form`. These are, respectively, the
`CGI` object, the user's `CGI::Session`, and a `CGI::FormBuilder`.
Each time a form is set up, the formbuilder_setup hook is called.
Typically the formbuilder_setup hook will check the form's title, and if
it's a form that it needs to modify, will call various methods to
add/remove/change fields, tweak the validation code for the fields, etc. It
will not validate or display the form.
Form validation and display can be overridden by the formbuilder hook.
By default, ikiwiki will do a basic validation and display of the form,
but if this hook is registered, it will stop that and let the hook take
over. This hook is passed an additional named parameter: `buttons` is an
array of the submit buttons for the form.
### savestate ### savestate
hook(type => "savestate", id => "foo", call => \&savestate); hook(type => "savestate", id => "foo", call => \&savestate);
@ -259,8 +285,8 @@ appear on the wiki page, rather than calling error().
#### `template($;@)` #### `template($;@)`
Creates and returns a HTML::Template object. The first parameter is the Creates and returns a [[cpan HTML::Template]] object. The first parameter
name of the file in the template directory. The optional remaining is the name of the file in the template directory. The optional remaining
parameters are passed to HTML::Template->new. parameters are passed to HTML::Template->new.
#### `htmlpage($)` #### `htmlpage($)`