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