]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blob - IkiWiki/Plugin/openid.pm
cc4b4ba3d4949c737d0d2139729a25c971743ff1
[git.ikiwiki.info.git] / IkiWiki / Plugin / openid.pm
1 #!/usr/bin/perl
2 # OpenID support.
3 package IkiWiki::Plugin::openid;
5 use warnings;
6 use strict;
7 use IkiWiki 3.00;
9 sub import {
10         hook(type => "getsetup", id => "openid", call => \&getsetup);
11         hook(type => "auth", id => "openid", call => \&auth);
12         hook(type => "formbuilder_setup", id => "openid",
13                 call => \&formbuilder_setup, last => 1);
14         IkiWiki::loadplugin("loginselector");
15         IkiWiki::Plugin::loginselector::register_login_plugin(
16                 "openid",
17                 \&openid_setup,
18                 \&openid_check_input,
19                 \&openid_auth,
20         );
21 }
23 sub getsetup () {
24         return
25                 plugin => {
26                         safe => 1,
27                         rebuild => 0,
28                         section => "auth",
29                 },
30                 openid_realm => {
31                         type => "string",
32                         description => "url pattern of openid realm (default is cgiurl)",
33                         safe => 0,
34                         rebuild => 0,
35                 },
36                 openid_cgiurl => {
37                         type => "string",
38                         description => "url to ikiwiki cgi to use for openid authentication (default is cgiurl)",
39                         safe => 0,
40                         rebuild => 0,
41                 },
42 }
44 sub openid_setup ($$) {
45         my $q=shift;
46         my $template=shift;
48         if (load_openid_module()) {
49                 my $openid_url=$q->param('openid_identifier');
50                 if (defined $openid_url) {
51                         $template->param(openid_url => $openid_url);
52                 }
53                 return 1;
54         }
55         else {
56                 return 0;
57         }
58 }
60 sub openid_check_input ($) {
61         my $q=shift;
62         my $openid_url=$q->param('openid_identifier');
63         defined $q->param("action") && $q->param("action") eq "verify" && defined $openid_url && length $openid_url;
64 }
66 sub openid_auth ($$$$) {
67         my $q=shift;
68         my $session=shift;
69         my $errordisplayer=shift;
70         my $openid_url=$q->param('openid_identifier');
71         validate($q, $session, $openid_url, $errordisplayer);
72 }
74 sub formbuilder_setup (@) {
75         my %params=@_;
77         my $form=$params{form};
78         my $session=$params{session};
79         my $cgi=$params{cgi};
80         
81         if ($form->title eq "preferences" &&
82                IkiWiki::openiduser($session->param("name"))) {
83                 $form->field(name => "openid_identifier", disabled => 1,
84                         label => htmllink("", "", "ikiwiki/OpenID", noimageinline => 1),
85                         value => "", 
86                         size => 1, force => 1,
87                         fieldset => "login",
88                         comment => $session->param("name"));
89         }
90 }
92 sub validate ($$$;$) {
93         my $q=shift;
94         my $session=shift;
95         my $openid_url=shift;
96         my $errhandler=shift;
98         my $csr=getobj($q, $session);
100         my $claimed_identity = $csr->claimed_identity($openid_url);
101         if (! $claimed_identity) {
102                 if ($errhandler) {
103                         if (ref($errhandler) eq 'CODE') {
104                                 $errhandler->($csr->err);
105                         }
106                         return 0;
107                 }
108                 else {
109                         error($csr->err);
110                 }
111         }
113         # Ask for client to provide a name and email, if possible.
114         # Try sreg and ax
115         if ($claimed_identity->can("set_extension_args")) {
116                 $claimed_identity->set_extension_args(
117                         'http://openid.net/extensions/sreg/1.1',
118                         {
119                                 optional => 'email,fullname,nickname',
120                         },
121                 );
122                 $claimed_identity->set_extension_args(
123                         'http://openid.net/srv/ax/1.0',
124                         {
125                                 mode => 'fetch_request',
126                                 'required' => 'email,fullname,nickname,firstname',
127                                 'type.email' => "http://schema.openid.net/contact/email",
128                                 'type.fullname' => "http://axschema.org/namePerson",
129                                 'type.nickname' => "http://axschema.org/namePerson/friendly",
130                                 'type.firstname' => "http://axschema.org/namePerson/first",
131                         },
132                 );
133         }
135         my $cgiurl=$config{openid_cgiurl};
136         $cgiurl=$q->url if ! defined $cgiurl;
138         my $trust_root=$config{openid_realm};
139         $trust_root=$cgiurl if ! defined $trust_root;
141         my $check_url = $claimed_identity->check_url(
142                 return_to => auto_upgrade_https($q, "$cgiurl?do=postsignin"),
143                 trust_root => auto_upgrade_https($q, $trust_root),
144                 delayed_return => 1,
145         );
146         # Redirect the user to the OpenID server, which will
147         # eventually bounce them back to auth()
148         IkiWiki::redirect($q, $check_url);
149         exit 0;
152 sub auth ($$) {
153         my $q=shift;
154         my $session=shift;
156         if (defined $q->param('openid.mode')) {
157                 my $csr=getobj($q, $session);
159                 if (my $setup_url = $csr->user_setup_url) {
160                         IkiWiki::redirect($q, $setup_url);
161                 }
162                 elsif ($csr->user_cancel) {
163                         IkiWiki::redirect($q, IkiWiki::baseurl(undef));
164                 }
165                 elsif (my $vident = $csr->verified_identity) {
166                         $session->param(name => $vident->url);
168                         my @extensions;
169                         if ($vident->can("signed_extension_fields")) {
170                                 @extensions=grep { defined } (
171                                         $vident->signed_extension_fields('http://openid.net/extensions/sreg/1.1'),
172                                         $vident->signed_extension_fields('http://openid.net/srv/ax/1.0'),
173                                 );
174                         }
175                         my $nickname;
176                         foreach my $ext (@extensions) {
177                                 foreach my $field (qw{value.email email}) {
178                                         if (exists $ext->{$field} &&
179                                             defined $ext->{$field} &&
180                                             length $ext->{$field}) {
181                                                 $session->param(email => $ext->{$field});
182                                                 if (! defined $nickname &&
183                                                     $ext->{$field}=~/(.+)@.+/) {
184                                                         $nickname = $1;
185                                                 }
186                                                 last;
187                                         }
188                                 }
189                                 foreach my $field (qw{value.nickname nickname value.fullname fullname value.firstname}) {
190                                         if (exists $ext->{$field} &&
191                                             defined $ext->{$field} &&
192                                             length $ext->{$field}) {
193                                                 $nickname=$ext->{$field};
194                                                 last;
195                                         }
196                                 }
197                         }
198                         if (defined $nickname) {
199                                 $session->param(nickname =>
200                                         Encode::decode_utf8($nickname));
201                         }
202                 }
203                 else {
204                         error("OpenID failure: ".$csr->err);
205                 }
206         }
207         elsif (defined $q->param('openid_identifier')) {
208                 # myopenid.com affiliate support
209                 validate($q, $session, scalar $q->param('openid_identifier'));
210         }
213 sub getobj ($$) {
214         my $q=shift;
215         my $session=shift;
217         eval q{use Net::INET6Glue::INET_is_INET6}; # may not be available
218         eval q{use Net::OpenID::Consumer};
219         error($@) if $@;
221         my $ua;
222         eval q{use LWPx::ParanoidAgent};
223         if (! $@) {
224                 $ua=LWPx::ParanoidAgent->new(agent => $config{useragent});
225         }
226         else {
227                 $ua=useragent();
228         }
230         # Store the secret in the session.
231         my $secret=$session->param("openid_secret");
232         if (! defined $secret) {
233                 $secret=rand;
234                 $session->param(openid_secret => $secret);
235         }
236         
237         my $cgiurl=$config{openid_cgiurl};
238         $cgiurl=$q->url if ! defined $cgiurl;
240         return Net::OpenID::Consumer->new(
241                 ua => $ua,
242                 args => $q,
243                 consumer_secret => sub { return shift()+$secret },
244                 required_root => auto_upgrade_https($q, $cgiurl),
245         );
248 sub auto_upgrade_https {
249         my $q=shift;
250         my $url=shift;
251         if ($q->https()) {
252                 $url=~s/^http:/https:/i;
253         }
254         return $url;
257 sub load_openid_module {
258         # Give up if module is unavailable to avoid needing to depend on it.
259         eval q{use Net::OpenID::Consumer};
260         if ($@) {
261                 debug("unable to load Net::OpenID::Consumer, not enabling OpenID login ($@)");
262                 return;
263         }
264         return 1;