PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/web/login.php

https://github.com/mysociety/hearfromyourmp
PHP | 420 lines | 309 code | 35 blank | 76 comment | 48 complexity | f0afdb681ad083d02b1b1933230c1d22 MD5 | raw file
  1. <?php
  2. /*
  3. * login.php:
  4. * Identification and authentication of users.
  5. *
  6. * The important thing here is that we mustn't leak information about whether
  7. * a given email address has an account or not. That means that, until we have
  8. * either processed a password, or have had the user click through from an
  9. * email token, we can't give any indication of whether the user has an account
  10. * or not.
  11. *
  12. * There are a number of pages here:
  13. *
  14. * login
  15. * Shown when the user doesn't have a cookie and login is needed. Either
  16. * solicit a password or allow the user to click a button to get sent an
  17. * email with a token in it. Supplied with parameters: stash, the stash
  18. * key for the request which should complete once the user has logged in;
  19. * email, the user's email address; and optionally name, the user's real
  20. * name.
  21. *
  22. * login-error
  23. * Shown when the user enters an incorrect password or an unknown email
  24. * address on the login page.
  25. *
  26. * create-password
  27. * Shown when a user logs in by means of an emailed token and has already
  28. * created or signed a pledge, or posted a comment, to ask them to give a
  29. * password for future logins.
  30. *
  31. * change-name
  32. * Shown when a user logs in but their name is significantly different
  33. * from the name shown on their account. Gives them the options of
  34. * changing the name recorded, or continuing with the old name.
  35. *
  36. * Copyright (c) 2005 UK Citizens Online Democracy. All rights reserved.
  37. * Email: chris@mysociety.org; WWW: http://www.mysociety.org/
  38. *
  39. * $Id: login.php,v 1.16 2007-11-01 15:05:22 matthew Exp $
  40. *
  41. */
  42. require_once '../phplib/ycml.php';
  43. require_once '../phplib/fns.php';
  44. require_once '../phplib/constituent.php';
  45. require_once '../commonlib/phplib/auth.php';
  46. require_once '../commonlib/phplib/person.php';
  47. require_once '../commonlib/phplib/stash.php';
  48. require_once '../commonlib/phplib/importparams.php';
  49. /* As a first step try to set a cookie and read it on redirect, so that we can
  50. * warn the user explicitly if they appear to be refusing cookies. */
  51. if (!array_key_exists('test_cookie', $_COOKIE)) {
  52. if (array_key_exists('test_cookie', $_GET)) {
  53. page_header("Please enable cookies");
  54. ?>
  55. <p>It appears that you don't have "cookies" enabled in your browser.
  56. <strong>To continue, you must enable cookies</strong>. Please
  57. read <a href="http://www.google.com/cookies.html">this page from Google
  58. explaining how to do that</a>, and then click the "back" button and
  59. try again</p>
  60. <?
  61. page_footer();
  62. exit();
  63. } else {
  64. setcookie('test_cookie', '1', 0, '/', person_cookie_domain());
  65. header("Location: /login?" . $_SERVER['QUERY_STRING'] . "&test_cookie=1\n");
  66. exit();
  67. }
  68. }
  69. /* Get all the parameters which we might use. */
  70. importparams(
  71. array('stash', '/^[0-9a-f]+$/', '', null),
  72. array('email', '/^[^@]+@[^@]+$/', '', null),
  73. array('name', '//', '', null),
  74. array('password', '/[^\s]/', '', null),
  75. array('t', '/^.+$/', '', null),
  76. array('rememberme', '/./', '', false),
  77. /* Buttons on login page. */
  78. array('LogIn', '/^.+$/', '', false),
  79. array('SendEmail', '/^.+$/', '', false),
  80. array('SetPassword', '/^.+$/', '', false),
  81. array('NoPassword', '/^.+$/', '', false),
  82. /* Buttons on name change page */
  83. array('KeepName', '/^.+$/', '', false),
  84. array('ChangeName', '/^.+$/', '', false)
  85. );
  86. if ($q_name=='<Enter your name>') {
  87. $q_name=null;
  88. }
  89. /* General purpose login, asks for email also. */
  90. if (get_http_var("now")) {
  91. $P = person_signon(array(
  92. 'reason_web' => "To log into HearFromYourMP, we need to check your email address.",
  93. 'reason_email' => "Then you will be logged into HearFromYourMP, and can set or change your password.",
  94. 'reason_email_subject' => 'Log into HearFromYourMP'
  95. ));
  96. page_header("Logged in");
  97. print "You're now logged in as <strong>";
  98. if ($P->has_name())
  99. print htmlspecialchars($P->name);
  100. else
  101. print htmlspecialchars($P->email);
  102. print "</strong>. Enjoy using HearFromYourMP!";
  103. page_footer();
  104. exit;
  105. }
  106. /* Do token case first because if the user isn't logged in *and* has a token
  107. * (unlikely but possible) the other branch would fail for lack of a stash
  108. * parameter. */
  109. if (!is_null($q_t)) {
  110. /* Process emailed token */
  111. $d = auth_token_retrieve('login', $q_t);
  112. if (!$d)
  113. err("Please check the URL (i.e. the long code of letters and numbers) is copied correctly from your email, as the token $q_t was not found.");
  114. $P = person_get($d['email']);
  115. if (is_null($P)) {
  116. if (!$d['name']) {
  117. page_header('Subscribe to HearFromYourMP');
  118. print '<div id="errors">You do not appear to be registered. Please sign up!</div>';
  119. constituent_subscribe_box(array('email'=>$d['email']));
  120. page_footer();
  121. exit();
  122. } else {
  123. $P = person_get_or_create($d['email'], $d['name']);
  124. }
  125. }
  126. $P->inc_numlogins();
  127. db_commit();
  128. /* Now give the user their cookie. */
  129. set_login_cookie($P);
  130. /* Recover "parameters" from token. */
  131. $q_h_email = htmlspecialchars($q_email = $d['email']);
  132. if (array_key_exists('name', $d) && !is_null($d['name'])) {
  133. $q_h_name = htmlspecialchars($q_name = $d['name']);
  134. } else {
  135. $q_h_name = $q_name = null;
  136. }
  137. $q_h_stash = htmlspecialchars($q_stash = $d['stash']);
  138. /* If the 'direct' key exists in the token, don't do any intervening
  139. * pages. */
  140. if (!array_key_exists('direct', $d)) {
  141. if ($q_name && !$P->matches_name($q_name))
  142. $P->name($q_name);
  143. /* Can set this to some condition if you don't want to always offer password */
  144. change_password_page($P);
  145. }
  146. stash_redirect($q_stash);
  147. /* NOTREACHED */
  148. }
  149. $P = person_if_signed_on();
  150. if (!is_null($P)) {
  151. /* Person is already signed in. */
  152. if ($q_SetPassword)
  153. change_password_page($P);
  154. if ($q_name && !$P->matches_name($q_name))
  155. /* ... but they have specified a name which differs from their recorded
  156. * name. Change it. */
  157. $P->name($q_name);
  158. if (!is_null($q_stash))
  159. /* No name change, just pass them through to the page they actually
  160. * wanted. */
  161. stash_redirect($q_stash);
  162. else {
  163. err('A required parameter was missing');
  164. }
  165. } elseif (is_null($q_stash)) {
  166. header("Location: /login?now=1");
  167. } else {
  168. /* Main login page. */
  169. login_page();
  170. }
  171. /* login_page
  172. * Render the login page, or respond to a button pressed on it. */
  173. function login_page() {
  174. global $q_stash, $q_email, $q_name, $q_LogIn, $q_SendEmail, $q_rememberme;
  175. if (is_null($q_stash)) {
  176. err("Required parameter was missing");
  177. }
  178. if ($q_LogIn) {
  179. /* User has tried to log in. */
  180. if (is_null($q_email)) {
  181. login_form(array('email'=>'Please enter your email address.'));
  182. exit();
  183. }
  184. if (!validate_email($q_email)) {
  185. login_form(array('email'=>'Please enter a valid email address'));
  186. exit();
  187. }
  188. global $q_password;
  189. $P = person_get($q_email);
  190. if (is_null($P) || !$P->check_password($q_password)) {
  191. login_form(array('badpass'=>'Either your email or password weren\'t recognised. Please try again.'));
  192. exit();
  193. } else {
  194. /* User has logged in correctly. Decide whether they are changing
  195. * their name. */
  196. set_login_cookie($P, $q_rememberme ? 28 * 24 * 3600 : null);
  197. if ($q_name && !$P->matches_name($q_name))
  198. $P->name($q_name);
  199. $P->inc_numlogins();
  200. db_commit();
  201. stash_redirect($q_stash);
  202. /* NOTREACHED */
  203. }
  204. } elseif ($q_SendEmail) {
  205. /* User has asked to be sent email. */
  206. if (is_null($q_email)) {
  207. login_form(array('email'=>'Please enter your email address.'));
  208. exit();
  209. }
  210. if (!validate_email($q_email)) {
  211. login_form(array('email'=>'Please enter a valid email address'));
  212. exit();
  213. }
  214. $token = auth_token_store('login', array(
  215. 'email' => $q_email,
  216. 'name' => $q_name,
  217. 'stash' => $q_stash,
  218. 'direct' => 1
  219. ));
  220. db_commit();
  221. $url = OPTION_BASE_URL . "/L/$token";
  222. $extra = stash_get_extra($q_stash);
  223. $template_data = rabx_unserialise($extra);
  224. $template_data['url'] = $url;
  225. $template_data['user_name'] = $q_name ? " $q_name" : '';
  226. $template_data['user_email'] = $q_email;
  227. $to = $q_name ? array($q_email, $q_name) : $q_email;
  228. ycml_send_email_template($to,
  229. array_key_exists('template', $template_data)
  230. ? $template_data['template'] : 'generic-confirm',
  231. $template_data);
  232. page_header("Now check your email");
  233. ?>
  234. <p id="loudmessage">
  235. Now check your email!<br>
  236. We've sent you an email, and you'll need to click the link in it before you can
  237. continue
  238. </p>
  239. <?
  240. page_footer(array('nonav' => 1));
  241. exit();
  242. /* NOTREACHED */
  243. } else {
  244. login_form();
  245. exit();
  246. }
  247. }
  248. /* login_form ERRORS
  249. * Print the login form. ERRORS is a list of errors encountered when the form
  250. * was processed. */
  251. function login_form($errors = array()) {
  252. /* Just render the form. */
  253. global $q_h_stash, $q_h_email, $q_h_name, $q_stash, $q_email, $q_name;
  254. page_header('Checking your email address');
  255. if (is_null($q_name))
  256. $q_name = $q_h_name = ''; /* shouldn't happen */
  257. $extra = stash_get_extra($q_stash);
  258. $template_data = rabx_unserialise($extra);
  259. $reason = htmlspecialchars($template_data['reason_web']);
  260. if (sizeof($errors)) {
  261. print '<div id="errors"><ul><li>';
  262. print join ('</li><li>', array_values($errors));
  263. print '</li></ul></div>';
  264. }
  265. /* Split into two forms to avoid "do you want to remember this
  266. * password" prompt in, e.g., Mozilla. */
  267. ?>
  268. <div class="pledge">
  269. <form action="/login" name="login" class="login" method="POST" accept-charset="utf-8">
  270. <input type="hidden" name="stash" value="<?=$q_h_stash?>">
  271. <input type="hidden" name="name" id="name" value="<?=$q_h_name?>">
  272. <p><strong><?=$reason?></strong></p>
  273. <ul>
  274. <li><p>We'll send an email to that address containing a link that logs you in.
  275. </p></li>
  276. <? if (is_null($q_email) || $errors) { ?>
  277. <li> Enter your email address: <input<? if (array_key_exists('email', $errors) || array_key_exists('badpass', $errors)) print ' class="error"' ?> type="text" size="30" name="email" id="email" value="<?=$q_h_email?>">
  278. <? } else { ?>
  279. <input type="hidden" name="email" value="<?=$q_h_email?>">
  280. <? } ?>
  281. <input type="submit" name="SendEmail" value="Go">
  282. </li>
  283. </ul>
  284. </form>
  285. </div>
  286. <?
  287. page_footer();
  288. }
  289. /* change_password_page PERSON
  290. * Show the logged-in PERSON a form to allow them to set or reset a password
  291. * for their account. */
  292. function change_password_page($P) {
  293. global $q_stash, $q_email, $q_name, $q_SetPassword, $q_NoPassword;
  294. global $q_h_stash, $q_h_email, $q_h_name;
  295. if (is_null($q_name))
  296. $q_name = $q_h_name = ''; /* shouldn't happen */
  297. $error = null;
  298. if ($q_SetPassword) {
  299. global $q_pw1, $q_pw2;
  300. importparams(
  301. array('pw1', '/[^\s]+/', '', null),
  302. array('pw2', '/[^\s]+/', '', null)
  303. );
  304. if (is_null($q_pw1) || is_null($q_pw2))
  305. $error = _("Please type your new password twice");
  306. elseif (strlen($q_pw1)<5 || strlen($q_pw2)<5)
  307. $error = _('Your password must be at least 5 characters long');
  308. elseif ($q_pw1 != $q_pw2)
  309. $error = _("Please type the same password twice");
  310. else {
  311. $P->password($q_pw1);
  312. db_commit();
  313. return;
  314. }
  315. } else if ($q_NoPassword)
  316. return;
  317. if ($P->has_password()) {
  318. page_header('Change your password');
  319. print <<<EOF
  320. <p>There is a password set for your email address on HearFromYourMP. Perhaps
  321. you've forgotten it? You can set a new password using this form:</p>
  322. EOF;
  323. } else {
  324. page_header('Set a password');
  325. print <<<EOF
  326. <p>On this page you can set a password which you can use to identify yourself
  327. to HearFromYourMP, so that you can post comments in the future without having
  328. to find an email from us first. You don't have to set a password if you don't want to.
  329. </p>
  330. EOF;
  331. }
  332. if (!is_null($error))
  333. print "<div id=\"errors\"><ul><li>$error</li><ul></div>";
  334. print <<<EOF
  335. <div class="pledge">
  336. <p><strong>Would you like to set a HearFromYourMP password?</strong></p>
  337. <ul>
  338. <li><form action="/login" name="loginNoPassword" class="login" method="POST" accept-charset="utf-8">
  339. <input type="hidden" name="stash" value="$q_h_stash">
  340. <input type="hidden" name="email" value="$q_h_email">
  341. <input type="hidden" name="name" value="$q_h_name">
  342. No, I don't want to think of a password right now.
  343. <input type="submit" name="NoPassword" value="Click here to continue">
  344. <br><small>(you can set a password another time)</small>
  345. </form></li>
  346. <li><form action="/login" name="loginSetPassword" class="login" method="POST" accept-charset="utf-8">
  347. <input type="hidden" name="stash" value="$q_h_stash">
  348. <input type="hidden" name="email" value="$q_h_email">
  349. <input type="hidden" name="name" value="$q_h_name">
  350. <input type="hidden" name="SetPassword" value="1">
  351. Yes, I'd like to set a password, so I don't have to keep going back to my email.
  352. <br>
  353. <strong>Password:</strong> <input type="password" name="pw1" id="pw1" size="15">
  354. <strong>Password (again):</strong> <input type="password" name="pw2" size="15">
  355. <input type="submit" name="SetPassword" value="Set password">
  356. </form>
  357. </li>
  358. </ul>
  359. </div>
  360. EOF;
  361. page_footer(array('nonav' => 1));
  362. exit();
  363. }
  364. /* set_login_cookie PERSON [DURATION]
  365. * Set a login cookie for the given PERSON. If set, EXPIRES is the time which
  366. * will be set for the cookie to expire; otherwise, a session cookie is set. */
  367. function set_login_cookie($P, $duration = null) {
  368. // error_log('set cookie');
  369. setcookie('pb_person_id', person_cookie_token($P->id(), $duration), is_null($duration) ? 0 : time() + $duration, '/', person_cookie_domain());
  370. }
  371. ?>