From 3e593eb9c0edd3f5cce7381ca145c0889441d719 Mon Sep 17 00:00:00 2001 From: joey Date: Mon, 20 Nov 2006 20:37:27 +0000 Subject: [PATCH] * 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. --- IkiWiki.pm | 10 +- IkiWiki/CGI.pm | 218 ++++++---------------------------- IkiWiki/Plugin/openid.pm | 95 ++++++++++----- IkiWiki/Plugin/skeleton.pm | 14 +++ debian/changelog | 10 +- doc/ikiwiki.setup | 2 +- doc/plugins.mdwn | 7 +- doc/plugins/passwordauth.mdwn | 9 ++ doc/plugins/write.mdwn | 44 +++++-- 9 files changed, 182 insertions(+), 227 deletions(-) create mode 100644 doc/plugins/passwordauth.mdwn diff --git a/IkiWiki.pm b/IkiWiki.pm index 15f7bcec0..703b596a8 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -62,7 +62,7 @@ sub defaultconfig () { #{{{ setup => undef, adminuser => undef, adminemail => undef, - plugin => [qw{mdwn inline htmlscrubber}], + plugin => [qw{mdwn inline htmlscrubber passwordauth}], timeformat => '%c', locale => undef, sslcookie => 0, @@ -663,7 +663,15 @@ sub run_hooks ($$) { # {{{ my $sub=shift; if (exists $hooks{$type}) { + my @deferred; 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}); } } diff --git a/IkiWiki/CGI.pm b/IkiWiki/CGI.pm index 14861e398..e1cb83b49 100644 --- a/IkiWiki/CGI.pm +++ b/IkiWiki/CGI.pm @@ -127,16 +127,9 @@ sub cgi_signin ($$) { #{{{ error($@) if $@; my $form = CGI::FormBuilder->new( title => "signin", - fields => [qw(do name password openid_url)], header => 1, charset => "utf-8", method => 'POST', - validate => { - confirm_password => { - perl => q{eq $form->field("password")}, - }, - email => 'EMAIL', - }, required => 'NONE', javascript => 0, params => $q, @@ -146,170 +139,32 @@ sub cgi_signin ($$) { #{{{ {template_params("signin.tmpl")} : ""), stylesheet => baseurl()."style.css", ); - - decode_form_utf8($form); + my $buttons=["Login"]; - $form->field(name => "name", required => 0, size => 30); $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} ? " | Get an OpenID" : "") - .')'); - } - 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) { $form->text("You need to log in first."); } - if ($form->submitted) { - my $submittype=$form->submitted; - # 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); - } + run_hooks(formbuilder_setup => sub { + shift->(form => $form, cgi => $q, session => $session); + }); - # Validate password differently depending on how - # 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")); - } - } + decode_form_utf8($form); - if ($form->submitted && $form->validate) { - if ($form->submitted eq 'Login') { - $session->param("name", $form->field("name")); - cgi_postsignin($q, $session); - } - 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"])); + if (exists $hooks{formbuilder}) { + run_hooks(formbuilder => sub { + shift->(form => $form, cgi => $q, session => $session, + buttons => $buttons); + }); } else { + if ($form->submitted) { + $form->validate; + } 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 $@; my $form = CGI::FormBuilder->new( title => "preferences", - fields => [qw(do name password confirm_password email - subscriptions locked_pages)], header => 0, charset => "utf-8", method => 'POST', validate => { - confirm_password => { - perl => q{eq $form->field("password")}, - }, email => 'EMAIL', }, required => 'NONE', @@ -357,30 +207,26 @@ sub cgi_prefs ($$) { #{{{ {template_params("prefs.tmpl")} : ""), 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 => "name", disabled => 1, - value => $user_name, force => 1, size => 30); - $form->field(name => "password", type => "password"); - $form->field(name => "confirm_password", type => "password"); + $form->field(name => "email", size => 50); $form->field(name => "subscriptions", size => 50, comment => "(".htmllink("", "", "PageSpec", 1).")"); $form->field(name => "locked_pages", size => 50, comment => "(".htmllink("", "", "PageSpec", 1).")"); $form->field(name => "banned_users", size => 50); + my $user_name=$session->param("name"); if (! is_admin($user_name)) { $form->field(name => "locked_pages", 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) { $form->field(name => "email", force => 1, value => userinfo_get($user_name, "email")); @@ -406,8 +252,8 @@ sub cgi_prefs ($$) { #{{{ return; } elsif ($form->submitted eq "Save Preferences" && $form->validate) { - foreach my $field (qw(password email subscriptions locked_pages)) { - if (length $form->field($field)) { + foreach my $field (qw(email subscriptions locked_pages)) { + if (defined $form->field($field) && length $form->field($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."); } - printheader($session); - print misctemplate($form->title, $form->render(submit => \@buttons)); + if (exists $hooks{formbuilder}) { + 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 ($$) { #{{{ @@ -454,6 +308,10 @@ sub cgi_editpage ($$) { #{{{ template => $renderer, ); + run_hooks(formbuilder_setup => sub { + shift->(form => $form, cgi => $q, session => $session); + }); + decode_form_utf8($form); # This untaint is safe because titlepage removes any problematic diff --git a/IkiWiki/Plugin/openid.pm b/IkiWiki/Plugin/openid.pm index 43ce8fd31..7ea67c5ca 100644 --- a/IkiWiki/Plugin/openid.pm +++ b/IkiWiki/Plugin/openid.pm @@ -8,8 +8,9 @@ use IkiWiki; sub import { #{{{ hook(type => "getopt", id => "openid", call => \&getopt); - hook(type => "checkconfig", id => "openid", call => \&checkconfig); hook(type => "auth", id => "openid", call => \&auth); + hook(type => "formbuilder_setup", id => "openid", + call => \&formbuilder_setup, last => 1); } # }}} sub getopt () { #{{{ @@ -19,37 +20,43 @@ sub getopt () { #{{{ GetOptions("openidsignup=s" => \$config{openidsignup}); } #}}} -sub checkconfig () { #{{{ - # Currently part of the OpenID code is in CGI.pm, and is enabled by - # this setting. - # TODO: modularise it all out into this plugin.. - $config{openid}=1; -} #}}} - -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); +sub formbuilder_setup (@) { #{{{ + my %params=@_; + + my $form=$params{form}; + my $session=$params{session}; + my $cgi=$params{cgi}; + + if ($form->title eq "signin") { + $form->field( + name => "openid_url", + label => "OpenID", + size => 30, + comment => '('. + htmllink("", "", "OpenID", 1, 0, "What's this?") + .($config{openidsignup} ? " | Get an OpenID" : "") + .')' + ); + + # Handle submission of an OpenID as validation. + if ($form->submitted && $form->submitted eq "Login" && + defined $form->field("openid_url") && + length $form->field("openid_url")) { + $form->field( + name => "openid_url", + validate => sub { + validate($cgi, $session, shift, $form); + }, + ); + # 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 ($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')) { - validate($q, $session, $q->param('openid_identifier')); } -} #}}} +} sub validate ($$$;$) { #{{{ my $q=shift; @@ -77,11 +84,37 @@ sub validate ($$$;$) { #{{{ delayed_return => 1, ); # 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); 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 ($$) { #{{{ my $q=shift; my $session=shift; diff --git a/IkiWiki/Plugin/skeleton.pm b/IkiWiki/Plugin/skeleton.pm index f3244ae14..feb0f7419 100644 --- a/IkiWiki/Plugin/skeleton.pm +++ b/IkiWiki/Plugin/skeleton.pm @@ -21,6 +21,8 @@ sub import { #{{{ hook(type => "change", id => "skeleton", call => \&change); hook(type => "cgi", id => "skeleton", call => \&cgi); 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); } # }}} @@ -103,6 +105,18 @@ sub 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 () { #{{{ debug("skeleton plugin running in savestate"); } #}}} diff --git a/debian/changelog b/debian/changelog index b622fa006..b6f397f27 100644 --- a/debian/changelog +++ b/debian/changelog @@ -14,8 +14,14 @@ ikiwiki (1.34) UNRELEASED; urgency=low * Add optional "desc" parameter to shortcut definitions. * Avoid locking the wiki at all when handling some basic cgi stuff (searches, recentchanges). - - -- Joey Hess Mon, 20 Nov 2006 06:54:12 -0500 + * 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 Mon, 20 Nov 2006 09:17:07 -0500 ikiwiki (1.33) unstable; urgency=low diff --git a/doc/ikiwiki.setup b/doc/ikiwiki.setup index 7d0eb71fe..c6aa08ddd 100644 --- a/doc/ikiwiki.setup +++ b/doc/ikiwiki.setup @@ -97,7 +97,7 @@ use IkiWiki::Setup::Standard { # wikitext camelcase pagestats htmltidy fortune # sidebar map rst toc linkmap openid}], # 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 # base page. diff --git a/doc/plugins.mdwn b/doc/plugins.mdwn index 6de17adc7..cd6037c2b 100644 --- a/doc/plugins.mdwn +++ b/doc/plugins.mdwn @@ -7,9 +7,10 @@ wiki, or just have [[type/fun]]. There's documentation if you want to [[write]] your own plugins, or you can install and use plugins contributed by others. -The [[mdwn]], [[inline]], and [[htmlscrubber]] plugins are enabled by default. -To enable other plugins, use the `--plugin` switch described in [[usage]], -or the equivalent `add_plugins` line in [[ikiwiki.setup]]. +The [[mdwn]], [[inline]], [[htmlscrubber]], and [[passwordauth]] plugins +are enabled by default. To enable other plugins, use the `--plugin` switch +described in [[usage]], or the equivalent `add_plugins` line in +[[ikiwiki.setup]]. # Plugin directory diff --git a/doc/plugins/passwordauth.mdwn b/doc/plugins/passwordauth.mdwn new file mode 100644 index 000000000..aded8829f --- /dev/null +++ b/doc/plugins/passwordauth.mdwn @@ -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]]. diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 651023701..f808d2e1e 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -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 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 In roughly the order they are called. @@ -119,13 +123,13 @@ return the htmlized content. hook(type => "pagetemplate", id => "foo", call => \&pagetemplate); -[[Templates]] are filled out for many different things in ikiwiki, -like generating a page, or part of a blog page, or an rss feed, or a cgi. -This hook allows modifying those templates. The function is passed named +[[Templates]] are filled out for many different things in ikiwiki, like +generating a page, or part of a blog page, or an rss feed, or a cgi. This +hook allows modifying those templates. The function is passed named parameters. The "page" and "destpage" parameters are the same as for a -preprocess hook. The "template" parameter is a `HTML::Template` object that -is the template that will be used to generate the page. The function can -manipulate that template object. +preprocess hook. The "template" parameter is a [[cpan HTML::Template]] +object that is the template that will be used to generate the page. The +function can manipulate that template object. The most common thing to do is probably to call $template->param() to add a new custom parameter to the template. @@ -177,7 +181,7 @@ terminate the program. ### 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 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, 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 hook(type => "savestate", id => "foo", call => \&savestate); @@ -259,8 +285,8 @@ appear on the wiki page, rather than calling error(). #### `template($;@)` -Creates and returns a HTML::Template object. The first parameter is the -name of the file in the template directory. The optional remaining +Creates and returns a [[cpan HTML::Template]] object. The first parameter +is the name of the file in the template directory. The optional remaining parameters are passed to HTML::Template->new. #### `htmlpage($)` -- 2.26.2