* Make auth methods pluggable.
authorjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Mon, 20 Nov 2006 01:52:18 +0000 (01:52 +0000)
committerjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Mon, 20 Nov 2006 01:52:18 +0000 (01:52 +0000)
* Move httpauth support to a plugin.
* Add an openid plugin to support logging in using OpenID.

15 files changed:
IkiWiki/CGI.pm
IkiWiki/Plugin/httpauth.pm [new file with mode: 0644]
IkiWiki/Plugin/openid.pm [new file with mode: 0644]
IkiWiki/Plugin/skeleton.pm
basewiki/style.css
debian/changelog
debian/control
doc/features.mdwn
doc/ikiwiki.setup
doc/plugins.mdwn
doc/plugins/httpauth.mdwn [new file with mode: 0644]
doc/plugins/map.mdwn
doc/plugins/openid.mdwn [new file with mode: 0644]
doc/plugins/type/auth.mdwn [new file with mode: 0644]
doc/plugins/write.mdwn

index ce4b6ada18ebd486a4ed12c2ae9f2e0b55097fb4..def0549c5d10367cb2dc7dd90f0f091e7028e0e8 100644 (file)
@@ -120,7 +120,7 @@ sub cgi_signin ($$) { #{{{
        error($@) if $@;
        my $form = CGI::FormBuilder->new(
                title => "signin",
-               fields => [qw(do title page subpage from name password)],
+               fields => [qw(do title page subpage from name password openid_url)],
                header => 1,
                charset => "utf-8",
                method => 'POST',
@@ -149,32 +149,56 @@ sub cgi_signin ($$) { #{{{
        $form->field(name => "from", type => "hidden");
        $form->field(name => "subpage", type => "hidden");
        $form->field(name => "password", type => "password", required => 0);
+       if ($config{openid}) {
+               $form->field(name => "openid_url", label => "OpenID", comment => "to log in via 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 title page subpage from 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, $form, shift);
+                               },
+                       );
+               }
+
                # 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{$form->submitted}}) {
+               foreach my $opt (@{$required{$submittype}}) {
                        $form->field(name => $opt, required => 1);
                }
        
                # Validate password differently depending on how
                # form was submitted.
-               if ($form->submitted eq 'Login') {
+               if ($submittype eq 'Login') {
                        $form->field(
                                name => "password",
                                validate => sub {
@@ -184,13 +208,13 @@ sub cgi_signin ($$) { #{{{
                        );
                        $form->field(name => "name", validate => '/^\w+$/');
                }
-               else {
+               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 ($form->submitted eq 'Create Account' ||
-                   $form->submitted eq 'Register') {
+               if ($submittype eq 'Create Account' ||
+                   $submittype eq 'Register') {
                        $form->field(
                                name => "name",
                                validate => sub {
@@ -201,7 +225,7 @@ sub cgi_signin ($$) { #{{{
                                },
                        );
                }
-               else {
+               elsif ($submittype ne 'OpenID') {
                        $form->field(
                                name => "name",
                                validate => sub {
@@ -229,8 +253,8 @@ sub cgi_signin ($$) { #{{{
                                        do => $form->field("do"),
                                        page => $form->field("page"),
                                        title => $form->field("title"),
-                                       subpage => $form->field("subpage"),
                                        from => $form->field("from"),
+                                       subpage => $form->field("subpage"),
                                ));
                        }
                        else {
@@ -680,11 +704,30 @@ sub cgi () { #{{{
                { FileName => "$config{wikistatedir}/sessions.db" });
        umask($oldmask);
        
+       # Auth hooks can sign a user in.
+       if ($do ne 'signin' && ! defined $session->param("name")) {
+               run_hooks(auth => sub {
+                       shift->($q, $session)
+               });
+               if (defined $session->param("name")) {
+                       # Make sure whatever user was authed is in the
+                       # userinfo db.
+                       if (! userinfo_get($session->param("name"), "regdate")) {
+                               userinfo_setall($session->param("name"), {
+                                       email => "",
+                                       password => "",
+                                       regdate => time,
+                               });
+                       }
+               }
+       }
+
        # Everything below this point needs the user to be signed in.
        if (((! $config{anonok} || $do eq 'prefs') &&
             (! $config{httpauth}) &&
             (! defined $session->param("name") ||
-            ! userinfo_get($session->param("name"), "regdate"))) || $do eq 'signin') {
+            ! userinfo_get($session->param("name"), "regdate")))
+            || $do eq 'signin') {
                cgi_signin($q, $session);
        
                # Force session flush with safe umask.
@@ -695,22 +738,6 @@ sub cgi () { #{{{
                return;
        }
 
-       if ($config{httpauth} && (! defined $session->param("name"))) {
-               if (! defined $q->remote_user()) {
-                       error("Could not determine authenticated username.");
-               }
-               else {
-                       $session->param("name", $q->remote_user());
-                       if (! userinfo_get($session->param("name"), "regdate")) {
-                               userinfo_setall($session->param("name"), {
-                                       email => "",
-                                       password => "",
-                                       regdate=>time,
-                               });
-                       }
-               }
-       }
-
        if (defined $session->param("name") && userinfo_get($session->param("name"), "banned")) {
                print $q->header(-status => "403 Forbidden");
                $session->delete();
diff --git a/IkiWiki/Plugin/httpauth.pm b/IkiWiki/Plugin/httpauth.pm
new file mode 100644 (file)
index 0000000..336eb79
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/perl
+# HTTP basic auth plugin.
+package IkiWiki::Plugin::httpauth;
+
+use warnings;
+use strict;
+use IkiWiki;
+
+sub import { #{{{
+       hook(type => "auth", id => "skeleton", call => \&auth);
+} # }}}
+
+sub auth ($$) { #{{{
+       my $cgi=shift;
+       my $session=shift;
+
+       if (defined $cgi->remote_user()) {
+               $session->param("name", $cgi->remote_user());
+       }
+} #}}}
+
+1
diff --git a/IkiWiki/Plugin/openid.pm b/IkiWiki/Plugin/openid.pm
new file mode 100644 (file)
index 0000000..55b1c4b
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/perl
+# OpenID support.
+package IkiWiki::Plugin::openid;
+
+use warnings;
+use strict;
+use IkiWiki;
+
+sub import { #{{{
+       hook(type => "checkconfig", id => "smiley", call => \&checkconfig);
+       hook(type => "auth", id => "skeleton", call => \&auth);
+} # }}}
+
+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);
+               }
+               elsif ($csr->user_cancel) {
+                       IkiWiki::redirect($q, $config{url});
+               }
+               elsif (my $vident = $csr->verified_identity) {
+                       $session->param(name => $vident->url);
+               }
+       }
+} #}}}
+
+sub validate ($$$$) { #{{{
+       my $q=shift;
+       my $session=shift;
+       my $form=shift;
+       my $openid_url=shift;
+
+       my $csr=getobj($q, $session);
+
+       my $claimed_identity = $csr->claimed_identity($openid_url);
+       if (! $claimed_identity) {
+               # Put the error in the form and fail validation.
+               $form->field(name => "openid_url", comment => $csr->err);
+               return 0;
+       }
+       my $check_url = $claimed_identity->check_url(
+               return_to => IkiWiki::cgiurl(
+                       do => $form->field("do"),
+                       page => $form->field("page"),
+                       title => $form->field("title"),
+                       from => $form->field("from"),
+                       subpage => $form->field("subpage")
+               ),
+               trust_root => $config{cgiurl},
+               delayed_return => 1,
+       );
+       # Redirect the user to the OpenID server, which will
+       # eventually bounce them back to auth() above.
+       IkiWiki::redirect($q, $check_url);
+       exit 0;
+} #}}}
+
+sub getobj ($$) { #{{{
+       my $q=shift;
+       my $session=shift;
+
+       eval q{use Net::OpenID::Consumer};
+       error($@) if $@;
+
+       my $ua;
+       eval q{use LWPx::ParanoidAgent};
+       if (! $@) {
+               $ua=LWPx::ParanoidAgent->new;
+       }
+       else {
+               $ua=LWP::UserAgent->new;
+       }
+
+       # Store the secret in the session.
+       my $secret=$session->param("openid_secret");
+       if (! defined $secret) {
+               $secret=$session->param(openid_secret => time);
+       }
+
+       return Net::OpenID::Consumer->new(
+               ua => $ua,
+               args => $q,
+               consumer_secret => $secret,
+               required_root => $config{cgiurl},
+       );
+} #}}}
+
+1
index acac16c1a36bf97407558df6278f3cd6a77ce8e5..f3244ae14ce05bd7b9fff85acb21a15ed1ddb9a7 100644 (file)
@@ -20,6 +20,7 @@ sub import { #{{{
        hook(type => "delete", id => "skeleton", call => \&delete);
        hook(type => "change", id => "skeleton", call => \&change);
        hook(type => "cgi", id => "skeleton", call => \&cgi);
+       hook(type => "auth", id => "skeleton", call => \&auth);
        hook(type => "savestate", id => "savestate", call => \&savestate);
 } # }}}
 
@@ -95,6 +96,13 @@ sub cgi ($) { #{{{
        debug("skeleton plugin running in cgi");
 } #}}}
 
+sub auth ($$) { #{{{
+       my $cgi=shift;
+       my $session=shift;
+
+       debug("skeleton plugin running in auth");
+} #}}}
+
 sub savestate () { #{{{
        debug("skeleton plugin running in savestate");
 } #}}}
index 0334670e1273bcf13d414f245d9bac8ee258c619..032665e7056d4ca86c87893cba065a73d7cf3f55 100644 (file)
@@ -205,3 +205,11 @@ li.L7 {
 li.L8 {
        list-style: upper-alpha;
 }
+
+input#openid_url {
+       background: url(http://openid.net/login-bg.gif) no-repeat;
+       background-color: #fff;
+       background-position: 0 50%;
+       color: #000;
+       padding-left: 18px;
+}
index afa0d9228c83dcca955907fb60b51292afe78644..f261f92c95b636e114e484d048ab77da658ad9bb 100644 (file)
@@ -1,3 +1,11 @@
+ikiwiki (1.34) UNRELEASED; urgency=low
+
+  * Make auth methods pluggable.
+  * Move httpauth support to a plugin.
+  * Add an openid plugin to support logging in using OpenID.
+
+ -- Joey Hess <joeyh@debian.org>  Sun, 19 Nov 2006 16:40:26 -0500
+
 ikiwiki (1.33) unstable; urgency=low
 
   * Fix issue with aggregate plugin updating expired pages.
index 074a2922e107c264471d64952e650253726c57a8..710e24185e92995ab484ba09184ff88ebb81703c 100644 (file)
@@ -11,7 +11,7 @@ Package: ikiwiki
 Architecture: all
 Depends: ${perl:Depends}, libxml-simple-perl, markdown, libtimedate-perl, libhtml-template-perl, libhtml-scrubber-perl, libcgi-formbuilder-perl (>= 3.02.02), libtime-duration-perl, libcgi-session-perl (>= 4.14-1), libmail-sendmail-perl, gcc | c-compiler, libc6-dev | libc-dev, libhtml-parser-perl, liburi-perl
 Recommends: subversion | git-core | tla | mercurial, hyperestraier
-Suggests: viewcvs, librpc-xml-perl, libtext-wikiformat-perl, python-docutils, polygen, tidy, libxml-feed-perl, libmailtools-perl, perlmagick, libfile-mimeinfo-perl
+Suggests: viewcvs, librpc-xml-perl, libtext-wikiformat-perl, python-docutils, polygen, tidy, libxml-feed-perl, libmailtools-perl, perlmagick, libfile-mimeinfo-perl, libnet-openid-consumer-perl, libcrypt-ssleay-perl
 Description: a wiki compiler
  ikiwiki converts a directory full of wiki pages into html pages suitable
  for publishing on a website. Unlike many wikis, ikiwiki does not have its
index cb118f27d4eee5c1a8d050a432974dd034f5b8f9..05aeebfc9cf93ef15244af1445422c62b6f0e7c5 100644 (file)
@@ -129,7 +129,11 @@ and can be enabled by enabling [[CGI]].
 ### User registration
 
 Can optionally be configured to allow only registered users to post
-pages; online user registration form, etc.
+pages.
+
+User registration can be done using a web form, or ikiwiki can be
+configured to accept users authenticated with OpenID, or HTTP basic
+authentication, or other methods implemented via plugins.
 
 ### Discussion pages
 
index 7cff65c5b1d0d333cd4178c4c4e7a41b904f1e18..6eb6446e0c0e9d4813613584326332db3cfd7ff7 100644 (file)
@@ -88,8 +88,6 @@ use IkiWiki::Setup::Standard {
        #locale => 'en_US.UTF-8',
        # Only send cookies over SSL connections.
        #sslcookie => 1,
-       # Use HTTP Authentication instead of Ikiwiki's.
-       #httpauth => 1,
        # Logging settings:
        verbose => 0,
        syslog => 0,
@@ -97,7 +95,7 @@ use IkiWiki::Setup::Standard {
        # To add plugins, list them here.
        #add_plugins => [qw{meta tag pagecount brokenlinks search smiley
        #                   wikitext camelcase pagestats htmltidy fortune
-       #                   sidebar map rst toc linkmap}],
+       #                   sidebar map rst toc linkmap openid}],
        # If you want to disable any of the default plugins, list them here.
        #disable_plugins => [qw{inline htmlscrubber}],
 
index 45e380d5860e04d05848f44bb8bb8f7b90395564..6de17adc768bf841b0c0da2facb68a95829262dc 100644 (file)
@@ -1,7 +1,8 @@
 Most of ikiwiki's [[features]] are implemented as plugins. Beyond the
 [[type/core]] features, there are plugins to [[type/format]] text,
-use [[type/tags]], show [[type/meta]] information, do other [[type/useful]]
-stuff, add [[type/chrome]] to the wiki, or just have [[type/fun]].
+use [[type/tags]], show [[type/meta]] information, add [[type/auth]]
+methods, do other [[type/useful]] stuff, add [[type/chrome]] to the
+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. 
diff --git a/doc/plugins/httpauth.mdwn b/doc/plugins/httpauth.mdwn
new file mode 100644 (file)
index 0000000..9823357
--- /dev/null
@@ -0,0 +1,9 @@
+[[template id=plugin name=httpauth included=1 author="Alec Berryman"]]
+[[tag type/auth]]
+
+This plugin allows HTTP basic authentication to be used to log into the
+wiki. To use the plugin, your web server should be set up to perform HTTP
+basic authentiation. The authenticated user will be automatically signed
+into the wiki.
+
+This plugin is included in ikiwiki, but is not enabled by default.
index 01f61e5b3a081c4ea7acb993e774e1298ac8d069..592a20706859a753c8388198c31e871ff3424a9c 100644 (file)
@@ -12,7 +12,6 @@ Hint: To limit the map to displaying pages less than a certian level deep,
 use a [[PageSpec]] like this: `pages="* and !*/*/*"`
 
 This plugin is included in ikiwiki, but is not enabled by default.
-It was contributed by Alessandro Dotti Contra.
 
 If this plugin is enabled, here is a page map for the plugins section
 of this wiki:
diff --git a/doc/plugins/openid.mdwn b/doc/plugins/openid.mdwn
new file mode 100644 (file)
index 0000000..344be7d
--- /dev/null
@@ -0,0 +1,12 @@
+[[template id=plugin name=openid included=1 author="[[Joey]]"]]
+[[tag type/auth]]
+
+This plugin allows users to use their [OpenID](http://openid.net/) to log
+into the wiki.
+
+The plugin needs the `Net::OpenID::Consumer` perl module. The
+`LWPx::ParanoidAgent` perl module is used if available, for added
+security. Finally, the `Crypt::SSLeay` perl module is needed to support
+users entering "https" OpenID urls.
+
+This plugin is included in ikiwiki, but is not enabled by default.
diff --git a/doc/plugins/type/auth.mdwn b/doc/plugins/type/auth.mdwn
new file mode 100644 (file)
index 0000000..a6ae5e4
--- /dev/null
@@ -0,0 +1,2 @@
+These plugins add different authentication methods for logging in to the
+wiki.
index 5cace0911c1f2d6cf3b6f40e0c4371a838d8b1e2..7c4da8d5f943b7b1e1cb84dd9be5de61addfafa6 100644 (file)
@@ -175,6 +175,19 @@ called in turn, and passed a CGI object. The hook should examine the
 parameters, and if it will handle this CGI request, output a page and
 terminate the program.
 
+### cgi
+
+       hook(type => "cgi", 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
+auth hook is called in turn, and passed a CGI object and a session object. 
+
+If the hook is able to authenticate the user, it should set the session
+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.
+
 ### savestate
 
        hook(type => "savestate", id => "foo", call => \&savestate);