authController.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <?php
  2. /**
  3. * This controller handles action about authentication.
  4. */
  5. class FreshRSS_auth_Controller extends Minz_ActionController {
  6. /**
  7. * This action handles the login page.
  8. *
  9. * It forwards to the correct login page (form or Persona) or main page if
  10. * the user is already connected.
  11. */
  12. public function loginAction() {
  13. if (FreshRSS_Auth::hasAccess()) {
  14. Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
  15. }
  16. $auth_type = Minz_Configuration::authType();
  17. switch ($auth_type) {
  18. case 'form':
  19. Minz_Request::forward(array('c' => 'auth', 'a' => 'formLogin'));
  20. break;
  21. case 'persona':
  22. Minz_Request::forward(array('c' => 'auth', 'a' => 'personaLogin'));
  23. break;
  24. case 'http_auth':
  25. case 'none':
  26. // It should not happened!
  27. Minz_Error::error(404);
  28. default:
  29. // TODO load plugin instead
  30. Minz_Error::error(404);
  31. }
  32. }
  33. /**
  34. * This action handles form login page.
  35. *
  36. * If this action is reached through a POST request, username and password
  37. * are compared to login the current user.
  38. *
  39. * Parameters are:
  40. * - nonce (default: false)
  41. * - username (default: '')
  42. * - challenge (default: '')
  43. * - keep_logged_in (default: false)
  44. */
  45. public function formLoginAction() {
  46. invalidateHttpCache();
  47. $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js');
  48. Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime));
  49. if (Minz_Request::isPost()) {
  50. $nonce = Minz_Session::param('nonce');
  51. $username = Minz_Request::param('username', '');
  52. $challenge = Minz_Request::param('challenge', '');
  53. try {
  54. $conf = new FreshRSS_Configuration($username);
  55. } catch(Minz_Exception $e) {
  56. // $username is not a valid user, nor the configuration file!
  57. Minz_Log::warning('Login failure: ' . $e->getMessage());
  58. Minz_Request::bad(_t('invalid_login'),
  59. array('c' => 'auth', 'a' => 'login'));
  60. }
  61. $ok = FreshRSS_FormAuth::checkCredentials(
  62. $username, $conf->passwordHash, $nonce, $challenge
  63. );
  64. if ($ok) {
  65. // Set session parameter to give access to the user.
  66. Minz_Session::_param('currentUser', $username);
  67. Minz_Session::_param('passwordHash', $conf->passwordHash);
  68. FreshRSS_Auth::giveAccess();
  69. // Set cookie parameter if nedded.
  70. if (Minz_Request::param('keep_logged_in')) {
  71. FreshRSS_FormAuth::makeCookie($username, $conf->passwordHash);
  72. } else {
  73. FreshRSS_FormAuth::deleteCookie();
  74. }
  75. // All is good, go back to the index.
  76. Minz_Request::good(_t('login'),
  77. array('c' => 'index', 'a' => 'index'));
  78. } else {
  79. Minz_Log::warning('Password mismatch for' .
  80. ' user=' . $username .
  81. ', nonce=' . $nonce .
  82. ', c=' . $challenge);
  83. Minz_Request::bad(_t('invalid_login'),
  84. array('c' => 'auth', 'a' => 'login'));
  85. }
  86. }
  87. }
  88. /**
  89. * This action handles Persona login page.
  90. *
  91. * If this action is reached through a POST request, assertion from Persona
  92. * is verificated and user connected if all is ok.
  93. *
  94. * Parameter is:
  95. * - assertion (default: false)
  96. *
  97. * @todo: Persona system should be moved to a plugin
  98. */
  99. public function personaLoginAction() {
  100. $this->view->res = false;
  101. if (Minz_Request::isPost()) {
  102. $this->view->_useLayout(false);
  103. $assert = Minz_Request::param('assertion');
  104. $url = 'https://verifier.login.persona.org/verify';
  105. $params = 'assertion=' . $assert . '&audience=' .
  106. urlencode(Minz_Url::display(null, 'php', true));
  107. $ch = curl_init();
  108. $options = array(
  109. CURLOPT_URL => $url,
  110. CURLOPT_RETURNTRANSFER => TRUE,
  111. CURLOPT_POST => 2,
  112. CURLOPT_POSTFIELDS => $params
  113. );
  114. curl_setopt_array($ch, $options);
  115. $result = curl_exec($ch);
  116. curl_close($ch);
  117. $res = json_decode($result, true);
  118. $login_ok = false;
  119. $reason = '';
  120. if ($res['status'] === 'okay') {
  121. $email = filter_var($res['email'], FILTER_VALIDATE_EMAIL);
  122. if ($email != '') {
  123. $persona_file = DATA_PATH . '/persona/' . $email . '.txt';
  124. if (($current_user = @file_get_contents($persona_file)) !== false) {
  125. $current_user = trim($current_user);
  126. try {
  127. $conf = new FreshRSS_Configuration($current_user);
  128. $login_ok = strcasecmp($email, $conf->mail_login) === 0;
  129. } catch (Minz_Exception $e) {
  130. //Permission denied or conf file does not exist
  131. $reason = 'Invalid configuration for user ' .
  132. '[' . $current_user . '] ' . $e->getMessage();
  133. }
  134. }
  135. } else {
  136. $reason = 'Invalid email format [' . $res['email'] . ']';
  137. }
  138. } else {
  139. $reason = $res['reason'];
  140. }
  141. if ($login_ok) {
  142. Minz_Session::_param('currentUser', $current_user);
  143. Minz_Session::_param('mail', $email);
  144. FreshRSS_Auth::giveAccess();
  145. invalidateHttpCache();
  146. } else {
  147. Minz_Log::error($reason);
  148. $res = array();
  149. $res['status'] = 'failure';
  150. $res['reason'] = _t('invalid_login');
  151. }
  152. header('Content-Type: application/json; charset=UTF-8');
  153. $this->view->res = $res;
  154. }
  155. }
  156. /**
  157. * This action removes all accesses of the current user.
  158. */
  159. public function logoutAction() {
  160. invalidateHttpCache();
  161. FreshRSS_Auth::removeAccess();
  162. Minz_Request::good(_t('disconnected'),
  163. array('c' => 'index', 'a' => 'index'));
  164. }
  165. }