added getsetup hooks for all plugins up to recentchanges
[ikiwiki.git] / IkiWiki / Plugin / openid.pm
1 #!/usr/bin/perl
2 # OpenID support.
3 package IkiWiki::Plugin::openid;
4
5 use warnings;
6 use strict;
7 use IkiWiki 2.00;
8
9 sub import { #{{{
10         hook(type => "getopt", id => "openid", call => \&getopt);
11         hook(type => "getsetup", id => "openid", call => \&getsetup);
12         hook(type => "auth", id => "openid", call => \&auth);
13         hook(type => "formbuilder_setup", id => "openid",
14                 call => \&formbuilder_setup, last => 1);
15 } # }}}
16
17 sub getopt () { #{{{
18         eval q{use Getopt::Long};
19         error($@) if $@;
20         Getopt::Long::Configure('pass_through');
21         GetOptions("openidsignup=s" => \$config{openidsignup});
22 } #}}}
23
24 sub getsetup () { #{{{
25         return
26                 openidsignup => {
27                         type => "string",
28                         default => "",
29                         example => "http://myopenid.com/",
30                         description => "an url where users can signup for an OpenID",
31                         safe => 1,
32                         rebuild => 0,
33                 },
34 } #}}}
35
36 sub formbuilder_setup (@) { #{{{
37         my %params=@_;
38
39         my $form=$params{form};
40         my $session=$params{session};
41         my $cgi=$params{cgi};
42         
43         if ($form->title eq "signin") {
44                 # Give up if module is unavailable to avoid
45                 # needing to depend on it.
46                 eval q{use Net::OpenID::Consumer};
47                 if ($@) {
48                         debug("unable to load Net::OpenID::Consumer, not enabling OpenID login");
49                         return;
50                 }
51
52                 # This avoids it displaying a redundant label for the
53                 # OpenID fieldset.
54                 $form->fieldsets("OpenID");
55
56                 $form->field(
57                         name => "openid_url",
58                         label => gettext("Log in with")." ".htmllink("", "", "ikiwiki/OpenID", noimageinline => 1),
59                         fieldset => "OpenID",
60                         size => 30,
61                         comment => ($config{openidsignup} ? " | <a href=\"$config{openidsignup}\">".gettext("Get an OpenID")."</a>" : "")
62                 );
63
64                 # Handle submission of an OpenID as validation.
65                 if ($form->submitted && $form->submitted eq "Login" &&
66                     defined $form->field("openid_url") && 
67                     length $form->field("openid_url")) {
68                         $form->field(
69                                 name => "openid_url",
70                                 validate => sub {
71                                         validate($cgi, $session, shift, $form);
72                                 },
73                         );
74                         # Skip all other required fields in this case.
75                         foreach my $field ($form->field) {
76                                 next if $field eq "openid_url";
77                                 $form->field(name => $field, required => 0,
78                                         validate => '/.*/');
79                         }
80                 }
81         }
82         elsif ($form->title eq "preferences") {
83                 if (! defined $form->field(name => "name")) {
84                         $form->field(name => "OpenID", disabled => 1,
85                                 value => $session->param("name"), 
86                                 size => 50, force => 1,
87                                 fieldset => "login");
88                 }
89         }
90 }
91
92 sub validate ($$$;$) { #{{{
93         my $q=shift;
94         my $session=shift;
95         my $openid_url=shift;
96         my $form=shift;
97
98         my $csr=getobj($q, $session);
99
100         my $claimed_identity = $csr->claimed_identity($openid_url);
101         if (! $claimed_identity) {
102                 if ($form) {
103                         # Put the error in the form and fail validation.
104                         $form->field(name => "openid_url", comment => $csr->err);
105                         return 0;
106                 }
107                 else {
108                         error($csr->err);
109                 }
110         }
111
112         my $check_url = $claimed_identity->check_url(
113                 return_to => IkiWiki::cgiurl(do => "postsignin"),
114                 trust_root => $config{cgiurl},
115                 delayed_return => 1,
116         );
117         # Redirect the user to the OpenID server, which will
118         # eventually bounce them back to auth()
119         IkiWiki::redirect($q, $check_url);
120         exit 0;
121 } #}}}
122
123 sub auth ($$) { #{{{
124         my $q=shift;
125         my $session=shift;
126
127         if (defined $q->param('openid.mode')) {
128                 my $csr=getobj($q, $session);
129
130                 if (my $setup_url = $csr->user_setup_url) {
131                         IkiWiki::redirect($q, $setup_url);
132                 }
133                 elsif ($csr->user_cancel) {
134                         IkiWiki::redirect($q, $config{url});
135                 }
136                 elsif (my $vident = $csr->verified_identity) {
137                         $session->param(name => $vident->url);
138                 }
139                 else {
140                         error("OpenID failure: ".$csr->err);
141                 }
142         }
143         elsif (defined $q->param('openid_identifier')) {
144                 # myopenid.com affiliate support
145                 validate($q, $session, $q->param('openid_identifier'));
146         }
147 } #}}}
148
149 sub getobj ($$) { #{{{
150         my $q=shift;
151         my $session=shift;
152
153         eval q{use Net::OpenID::Consumer};
154         error($@) if $@;
155
156         my $ua;
157         eval q{use LWPx::ParanoidAgent};
158         if (! $@) {
159                 $ua=LWPx::ParanoidAgent->new;
160         }
161         else {
162                 $ua=LWP::UserAgent->new;
163         }
164
165         # Store the secret in the session.
166         my $secret=$session->param("openid_secret");
167         if (! defined $secret) {
168                 $secret=rand;
169                 $session->param(openid_secret => $secret);
170         }
171
172         return Net::OpenID::Consumer->new(
173                 ua => $ua,
174                 args => $q,
175                 consumer_secret => sub { return shift()+$secret },
176                 required_root => $config{cgiurl},
177         );
178 } #}}}
179
180 package IkiWiki;
181
182 # This is not used by this plugin, but this seems the best place to put it.
183 # Used elsewhere to pretty-display the name of an openid user.
184 sub openiduser ($) { #{{{
185         my $user=shift;
186
187         if ($user =~ m!^https?://! &&
188             eval q{use Net::OpenID::VerifiedIdentity; 1} && !$@) {
189                 my $oid=Net::OpenID::VerifiedIdentity->new(identity => $user);
190                 my $display=$oid->display;
191                 # Convert "user.somehost.com" to "user [somehost.com]".
192                 if ($display !~ /\[/) {
193                         $display=~s/^(.*?)\.([^.]+\.[a-z]+)$/$1 [$2]/;
194                 }
195                 # Convert "http://somehost.com/user" to "user [somehost.com]".
196                 if ($display !~ /\[/) {
197                         $display=~s/^https?:\/\/(.+)\/([^\/]+)$/$2 [$1]/;
198                 }
199                 $display=~s!^https?://!!; # make sure this is removed
200                 eval q{use CGI 'escapeHTML'};
201                 error($@) if $@;
202                 return escapeHTML($display);
203         }
204         return;
205 }
206
207 1