summaryrefslogtreecommitdiff
path: root/IkiWiki/Plugin/passwordauth.pm
blob: 68aaf808f5c91249ceee266ccadd9745cb4f9123 (plain)
  1. #!/usr/bin/perl
  2. # Ikiwiki password authentication.
  3. package IkiWiki::Plugin::passwordauth;
  4. use warnings;
  5. use strict;
  6. use IkiWiki 2.00;
  7. sub import { #{{{
  8. hook(type => "getsetup", id => "passwordauth", "call" => \&getsetup);
  9. hook(type => "formbuilder_setup", id => "passwordauth", call => \&formbuilder_setup);
  10. hook(type => "formbuilder", id => "passwordauth", call => \&formbuilder);
  11. hook(type => "sessioncgi", id => "passwordauth", call => \&sessioncgi);
  12. } # }}}
  13. sub getsetup () { #{{{
  14. return
  15. account_creation_password => {
  16. type => "string",
  17. default => "",
  18. description => "a password that must be entered when signing up for an account",
  19. safe => 1,
  20. rebuild => 0,
  21. },
  22. password_cost => {
  23. type => "integer",
  24. default => 8,
  25. description => "cost of generating a password using Authen::Passphrase::BlowfishCrypt",
  26. safe => 1,
  27. rebuild => 0,
  28. },
  29. } #}}}
  30. # Checks if a string matches a user's password, and returns true or false.
  31. sub checkpassword ($$;$) { #{{{
  32. my $user=shift;
  33. my $password=shift;
  34. my $field=shift || "password";
  35. # It's very important that the user not be allowed to log in with
  36. # an empty password!
  37. if (! length $password) {
  38. return 0;
  39. }
  40. my $userinfo=IkiWiki::userinfo_retrieve();
  41. if (! length $user || ! defined $userinfo ||
  42. ! exists $userinfo->{$user} || ! ref $userinfo->{$user}) {
  43. return 0;
  44. }
  45. my $ret=0;
  46. if (exists $userinfo->{$user}->{"crypt".$field}) {
  47. eval q{use Authen::Passphrase};
  48. error $@ if $@;
  49. my $p = Authen::Passphrase->from_crypt($userinfo->{$user}->{"crypt".$field});
  50. $ret=$p->match($password);
  51. }
  52. elsif (exists $userinfo->{$user}->{$field}) {
  53. $ret=$password eq $userinfo->{$user}->{$field};
  54. }
  55. if ($ret &&
  56. (exists $userinfo->{$user}->{resettoken} ||
  57. exists $userinfo->{$user}->{cryptresettoken})) {
  58. # Clear reset token since the user has successfully logged in.
  59. delete $userinfo->{$user}->{resettoken};
  60. delete $userinfo->{$user}->{cryptresettoken};
  61. IkiWiki::userinfo_store($userinfo);
  62. }
  63. return $ret;
  64. } #}}}
  65. sub setpassword ($$;$) { #{{{
  66. my $user=shift;
  67. my $password=shift;
  68. my $field=shift || "password";
  69. eval q{use Authen::Passphrase::BlowfishCrypt};
  70. if (! $@) {
  71. my $p = Authen::Passphrase::BlowfishCrypt->new(
  72. cost => $config{password_cost} || 8,
  73. salt_random => 1,
  74. passphrase => $password,
  75. );
  76. IkiWiki::userinfo_set($user, "crypt$field", $p->as_crypt);
  77. IkiWiki::userinfo_set($user, $field, "");
  78. }
  79. else {
  80. IkiWiki::userinfo_set($user, $field, $password);
  81. }
  82. } #}}}
  83. sub formbuilder_setup (@) { #{{{
  84. my %params=@_;
  85. my $form=$params{form};
  86. my $session=$params{session};
  87. my $cgi=$params{cgi};
  88. if ($form->title eq "signin" || $form->title eq "register") {
  89. $form->field(name => "name", required => 0);
  90. $form->field(name => "password", type => "password", required => 0);
  91. if ($form->submitted eq "Register" || $form->submitted eq "Create Account") {
  92. $form->field(name => "confirm_password", type => "password");
  93. $form->field(name => "account_creation_password", type => "password")
  94. if (defined $config{account_creation_password} &&
  95. length $config{account_creation_password});
  96. $form->field(name => "email", size => 50);
  97. $form->title("register");
  98. $form->text("");
  99. $form->field(name => "confirm_password",
  100. validate => sub {
  101. shift eq $form->field("password");
  102. },
  103. );
  104. $form->field(name => "password",
  105. validate => sub {
  106. shift eq $form->field("confirm_password");
  107. },
  108. );
  109. }
  110. if ($form->submitted) {
  111. my $submittype=$form->submitted;
  112. # Set required fields based on how form was submitted.
  113. my %required=(
  114. "Login" => [qw(name password)],
  115. "Register" => [],
  116. "Create Account" => [qw(name password confirm_password email)],
  117. "Reset Password" => [qw(name)],
  118. );
  119. foreach my $opt (@{$required{$submittype}}) {
  120. $form->field(name => $opt, required => 1);
  121. }
  122. if ($submittype eq "Create Account") {
  123. $form->field(
  124. name => "account_creation_password",
  125. validate => sub {
  126. shift eq $config{account_creation_password};
  127. },
  128. required => 1,
  129. ) if (defined $config{account_creation_password} &&
  130. length $config{account_creation_password});
  131. $form->field(
  132. name => "email",
  133. validate => "EMAIL",
  134. );
  135. }
  136. # Validate password against name for Login.
  137. if ($submittype eq "Login") {
  138. $form->field(
  139. name => "password",
  140. validate => sub {
  141. checkpassword($form->field("name"), shift);
  142. },
  143. );
  144. }
  145. elsif ($submittype eq "Register" ||
  146. $submittype eq "Create Account" ||
  147. $submittype eq "Reset Password") {
  148. $form->field(name => "password", validate => 'VALUE');
  149. }
  150. # And make sure the entered name exists when logging
  151. # in or sending email, and does not when registering.
  152. if ($submittype eq 'Create Account' ||
  153. $submittype eq 'Register') {
  154. $form->field(
  155. name => "name",
  156. validate => sub {
  157. my $name=shift;
  158. length $name &&
  159. $name=~/$config{wiki_file_regexp}/ &&
  160. ! IkiWiki::userinfo_get($name, "regdate");
  161. },
  162. );
  163. }
  164. elsif ($submittype eq "Login" ||
  165. $submittype eq "Reset Password") {
  166. $form->field(
  167. name => "name",
  168. validate => sub {
  169. my $name=shift;
  170. length $name &&
  171. IkiWiki::userinfo_get($name, "regdate");
  172. },
  173. );
  174. }
  175. }
  176. else {
  177. # First time settings.
  178. $form->field(name => "name");
  179. if ($session->param("name")) {
  180. $form->field(name => "name", value => $session->param("name"));
  181. }
  182. }
  183. }
  184. elsif ($form->title eq "preferences") {
  185. $form->field(name => "name", disabled => 1,
  186. value => $session->param("name"), force => 1,
  187. fieldset => "login");
  188. $form->field(name => "password", type => "password",
  189. fieldset => "login",
  190. validate => sub {
  191. shift eq $form->field("confirm_password");
  192. }),
  193. $form->field(name => "confirm_password", type => "password",
  194. fieldset => "login",
  195. validate => sub {
  196. shift eq $form->field("password");
  197. }),
  198. }
  199. }
  200. sub formbuilder (@) { #{{{
  201. my %params=@_;
  202. my $form=$params{form};
  203. my $session=$params{session};
  204. my $cgi=$params{cgi};
  205. my $buttons=$params{buttons};
  206. if ($form->title eq "signin" || $form->title eq "register") {
  207. if ($form->submitted && $form->validate) {
  208. if ($form->submitted eq 'Login') {
  209. $session->param("name", $form->field("name"));
  210. IkiWiki::cgi_postsignin($cgi, $session);
  211. }
  212. elsif ($form->submitted eq 'Create Account') {
  213. my $user_name=$form->field('name');
  214. if (IkiWiki::userinfo_setall($user_name, {
  215. 'email' => $form->field('email'),
  216. 'regdate' => time})) {
  217. setpassword($user_name, $form->field('password'));
  218. $form->field(name => "confirm_password", type => "hidden");
  219. $form->field(name => "email", type => "hidden");
  220. $form->text(gettext("Account creation successful. Now you can Login."));
  221. }
  222. else {
  223. error(gettext("Error creating account."));
  224. }
  225. }
  226. elsif ($form->submitted eq 'Reset Password') {
  227. my $user_name=$form->field("name");
  228. my $email=IkiWiki::userinfo_get($user_name, "email");
  229. if (! length $email) {
  230. error(gettext("No email address, so cannot email password reset instructions."));
  231. }
  232. # Store a token that can be used once
  233. # to log the user in. This needs to be hard
  234. # to guess. Generating a cgi session id will
  235. # make it as hard to guess as any cgi session.
  236. eval q{use CGI::Session};
  237. error($@) if $@;
  238. my $token = CGI::Session->new->id;
  239. setpassword($user_name, $token, "resettoken");
  240. my $template=template("passwordmail.tmpl");
  241. $template->param(
  242. user_name => $user_name,
  243. passwordurl => IkiWiki::cgiurl(
  244. 'do' => "reset",
  245. 'name' => $user_name,
  246. 'token' => $token,
  247. ),
  248. wikiurl => $config{url},
  249. wikiname => $config{wikiname},
  250. REMOTE_ADDR => $ENV{REMOTE_ADDR},
  251. );
  252. eval q{use Mail::Sendmail};
  253. error($@) if $@;
  254. sendmail(
  255. To => IkiWiki::userinfo_get($user_name, "email"),
  256. From => "$config{wikiname} admin <".(defined $config{adminemail} ? $config{adminemail} : "")>",
  257. Subject => "$config{wikiname} information",
  258. Message => $template->output,
  259. ) or error(gettext("Failed to send mail"));
  260. $form->text(gettext("You have been mailed password reset instructions."));
  261. $form->field(name => "name", required => 0);
  262. push @$buttons, "Reset Password";
  263. }
  264. elsif ($form->submitted eq "Register") {
  265. @$buttons="Create Account";
  266. }
  267. }
  268. elsif ($form->submitted eq "Create Account") {
  269. @$buttons="Create Account";
  270. }
  271. else {
  272. push @$buttons, "Register", "Reset Password";
  273. }
  274. }
  275. elsif ($form->title eq "preferences") {
  276. if ($form->submitted eq "Save Preferences" && $form->validate) {
  277. my $user_name=$form->field('name');
  278. if ($form->field("password") && length $form->field("password")) {
  279. setpassword($user_name, $form->field('password'));
  280. }
  281. }
  282. }
  283. } #}}}
  284. sub sessioncgi ($$) { #{{{
  285. my $q=shift;
  286. my $session=shift;
  287. if ($q->param('do') eq 'reset') {
  288. my $name=$q->param("name");
  289. my $token=$q->param("token");
  290. if (! defined $name || ! defined $token ||
  291. ! length $name || ! length $token) {
  292. error(gettext("incorrect password reset url"));
  293. }
  294. if (! checkpassword($name, $token, "resettoken")) {
  295. error(gettext("password reset denied"));
  296. }
  297. $session->param("name", $name);
  298. IkiWiki::cgi_prefs($q, $session);
  299. exit;
  300. }
  301. } #}}}
  302. 1