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