* Add "last" parameter to hook function. Very basic ordering, and hopefully
authorjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Mon, 20 Nov 2006 20:37:27 +0000 (20:37 +0000)
committerjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Mon, 20 Nov 2006 20:37:27 +0000 (20:37 +0000)
  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
IkiWiki/CGI.pm
IkiWiki/Plugin/openid.pm
IkiWiki/Plugin/skeleton.pm
debian/changelog
doc/ikiwiki.setup
doc/plugins.mdwn
doc/plugins/passwordauth.mdwn [new file with mode: 0644]
doc/plugins/write.mdwn

index 15f7bcec01e138c03b2349b972ac2d003e211572..703b596a8b9168c40e9e5abdb5c30f1bd9b6133c 100644 (file)
@@ -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});
                }
        }
index 14861e398d5e04fa5f050fe3e7626df8f3c3a44d..e1cb83b496f4d0209caf4b7ee5a1796e4330420c 100644 (file)
@@ -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} ? " | <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) {
                $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
index 43ce8fd31e8e6bbb950b10b367cf6bc4242b3614..7ea67c5ca4a34aeaac6f17d0fe23137878fb80e4 100644 (file)
@@ -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} ? " | <a href=\"$config{openidsignup}\">Get an OpenID</a>" : "")
+                               .')'
+               );
+
+               # 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;
index f3244ae14ce05bd7b9fff85acb21a15ed1ddb9a7..feb0f7419b26948624e1d3d96b5af3fc82e906a4 100644 (file)
@@ -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");
 } #}}}
index b622fa006396ac8c1e6665bdd74afd3966333021..b6f397f27030a3952f7410ae65c70a45245532e4 100644 (file)
@@ -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 <joeyh@debian.org>  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 <joeyh@debian.org>  Mon, 20 Nov 2006 09:17:07 -0500
 
 ikiwiki (1.33) unstable; urgency=low
 
index 7d0eb71fe32d5779937a896b7f47a98253edd903..c6aa08ddd0fa108f0d3586673e5a347dd8679152 100644 (file)
@@ -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.
index 6de17adc768bf841b0c0da2facb68a95829262dc..cd6037c2b442aabf6519cd066a164f766f1af205 100644 (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
 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 (file)
index 0000000..aded882
--- /dev/null
@@ -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]].
index 651023701f12da4971227373517b937899eb70aa..f808d2e1e089ac412cbabbc2a379e465ca19cbf4 100644 (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
 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($)`