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